changeset 2624:56f94936df1e

code style reformatting using black
author Goffi <goffi@goffi.org>
date Wed, 27 Jun 2018 20:14:46 +0200
parents 49533de4540b
children a55a14c3cbf4
files sat/__init__.py sat/bridge/bridge_constructor/base_constructor.py sat/bridge/bridge_constructor/bridge_constructor.py sat/bridge/bridge_constructor/constants.py sat/bridge/bridge_constructor/constructors/dbus-xml/constructor.py sat/bridge/bridge_constructor/constructors/dbus/constructor.py sat/bridge/bridge_constructor/constructors/dbus/dbus_core_template.py sat/bridge/bridge_constructor/constructors/embedded/constructor.py sat/bridge/bridge_constructor/constructors/embedded/embedded_frontend_template.py sat/bridge/bridge_constructor/constructors/embedded/embedded_template.py sat/bridge/bridge_constructor/constructors/mediawiki/constructor.py sat/bridge/bridge_constructor/constructors/pb/constructor.py sat/bridge/bridge_constructor/constructors/pb/pb_core_template.py sat/bridge/bridge_constructor/constructors/pb/pb_frontend_template.py sat/bridge/dbus_bridge.py sat/bridge/pb.py sat/core/constants.py sat/core/exceptions.py sat/core/i18n.py sat/core/log_config.py sat/core/sat_main.py sat/core/xmpp.py sat/memory/cache.py sat/memory/crypto.py sat/memory/disco.py sat/memory/memory.py sat/memory/params.py sat/plugins/__init__.py sat/plugins/plugin_adhoc_dbus.py sat/plugins/plugin_blog_import.py sat/plugins/plugin_blog_import_dokuwiki.py sat/plugins/plugin_blog_import_dotclear.py sat/plugins/plugin_comp_file_sharing.py sat/plugins/plugin_exp_command_export.py sat/plugins/plugin_exp_events.py sat/plugins/plugin_exp_jingle_stream.py sat/plugins/plugin_exp_lang_detect.py sat/plugins/plugin_exp_parrot.py sat/plugins/plugin_exp_pubsub_hook.py sat/plugins/plugin_exp_pubsub_schema.py sat/plugins/plugin_import.py sat/plugins/plugin_misc_account.py sat/plugins/plugin_misc_android.py sat/plugins/plugin_misc_debug.py sat/plugins/plugin_misc_extra_pep.py sat/plugins/plugin_misc_file.py sat/plugins/plugin_misc_groupblog.py sat/plugins/plugin_misc_identity.py sat/plugins/plugin_misc_imap.py sat/plugins/plugin_misc_ip.py sat/plugins/plugin_misc_nat-port.py sat/plugins/plugin_misc_quiz.py sat/plugins/plugin_misc_radiocol.py sat/plugins/plugin_misc_register_account.py sat/plugins/plugin_misc_room_game.py sat/plugins/plugin_misc_smtp.py sat/plugins/plugin_misc_static_blog.py sat/plugins/plugin_misc_tarot.py sat/plugins/plugin_misc_text_commands.py sat/plugins/plugin_misc_text_syntaxes.py sat/plugins/plugin_misc_tickets.py sat/plugins/plugin_misc_upload.py sat/plugins/plugin_misc_watched.py sat/plugins/plugin_misc_welcome.py sat/plugins/plugin_misc_xmllog.py sat/plugins/plugin_sec_otr.py sat/plugins/plugin_syntax_wiki_dotclear.py sat/plugins/plugin_tickets_import.py sat/plugins/plugin_tickets_import_bugzilla.py sat/plugins/plugin_tmp_directory_subscription.py sat/plugins/plugin_xep_0020.py sat/plugins/plugin_xep_0033.py sat/plugins/plugin_xep_0047.py sat/plugins/plugin_xep_0048.py sat/plugins/plugin_xep_0049.py sat/plugins/plugin_xep_0050.py sat/plugins/plugin_xep_0054.py sat/plugins/plugin_xep_0055.py sat/plugins/plugin_xep_0059.py sat/plugins/plugin_xep_0060.py sat/plugins/plugin_xep_0065.py sat/plugins/plugin_xep_0070.py sat/plugins/plugin_xep_0071.py sat/plugins/plugin_xep_0077.py sat/plugins/plugin_xep_0085.py sat/plugins/plugin_xep_0092.py sat/plugins/plugin_xep_0095.py sat/plugins/plugin_xep_0096.py sat/plugins/plugin_xep_0100.py sat/plugins/plugin_xep_0115.py sat/plugins/plugin_xep_0163.py sat/plugins/plugin_xep_0166.py sat/plugins/plugin_xep_0184.py sat/plugins/plugin_xep_0203.py sat/plugins/plugin_xep_0231.py sat/plugins/plugin_xep_0234.py sat/plugins/plugin_xep_0249.py sat/plugins/plugin_xep_0260.py sat/plugins/plugin_xep_0261.py sat/plugins/plugin_xep_0264.py sat/plugins/plugin_xep_0277.py sat/plugins/plugin_xep_0280.py sat/plugins/plugin_xep_0297.py sat/plugins/plugin_xep_0300.py sat/plugins/plugin_xep_0313.py sat/plugins/plugin_xep_0329.py sat/plugins/plugin_xep_0334.py sat/plugins/plugin_xep_0363.py sat/stdui/ui_contact_list.py sat/stdui/ui_profile_manager.py sat/test/constants.py sat/test/helpers_plugins.py sat/test/test_helpers_plugins.py sat/test/test_memory.py sat/test/test_memory_crypto.py sat/test/test_plugin_misc_groupblog.py sat/test/test_plugin_misc_radiocol.py sat/test/test_plugin_misc_room_game.py sat/test/test_plugin_misc_text_syntaxes.py sat/test/test_plugin_xep_0033.py sat/test/test_plugin_xep_0085.py sat/test/test_plugin_xep_0203.py sat/test/test_plugin_xep_0277.py sat/test/test_plugin_xep_0297.py sat/test/test_plugin_xep_0313.py sat/test/test_plugin_xep_0334.py sat/tools/common/ansi.py sat/tools/common/data_format.py sat/tools/common/data_objects.py sat/tools/common/date_utils.py sat/tools/common/dynamic_import.py sat/tools/common/files_utils.py sat/tools/common/regex.py sat/tools/common/template.py sat/tools/common/template_xmlui.py sat/tools/common/uri.py sat/tools/config.py sat/tools/email.py sat/tools/sat_defer.py sat/tools/stream.py sat/tools/trigger.py sat/tools/utils.py sat/tools/xml_tools.py sat_frontends/bridge/bridge_frontend.py sat_frontends/bridge/pb.py sat_frontends/jp/arg_tools.py sat_frontends/jp/cmd_account.py sat_frontends/jp/cmd_adhoc.py sat_frontends/jp/cmd_avatar.py sat_frontends/jp/cmd_blog.py sat_frontends/jp/cmd_debug.py sat_frontends/jp/cmd_event.py sat_frontends/jp/cmd_file.py sat_frontends/jp/cmd_forums.py sat_frontends/jp/cmd_identity.py sat_frontends/jp/cmd_input.py sat_frontends/jp/cmd_invitation.py sat_frontends/jp/cmd_merge_request.py sat_frontends/jp/cmd_message.py sat_frontends/jp/cmd_pipe.py sat_frontends/jp/cmd_pubsub.py sat_frontends/jp/cmd_shell.py sat_frontends/jp/cmd_ticket.py sat_frontends/jp/cmd_uri.py sat_frontends/jp/common.py sat_frontends/jp/constants.py sat_frontends/jp/output_std.py sat_frontends/jp/output_template.py sat_frontends/jp/output_xml.py sat_frontends/jp/output_xmlui.py sat_frontends/jp/xmlui_manager.py sat_frontends/primitivus/chat.py sat_frontends/primitivus/config.py sat_frontends/primitivus/constants.py sat_frontends/primitivus/contact_list.py sat_frontends/primitivus/game_tarot.py sat_frontends/primitivus/keys.py sat_frontends/primitivus/notify.py sat_frontends/primitivus/profile_manager.py sat_frontends/primitivus/progress.py sat_frontends/primitivus/status.py sat_frontends/primitivus/widget.py sat_frontends/primitivus/xmlui.py sat_frontends/quick_frontend/constants.py sat_frontends/quick_frontend/quick_app.py sat_frontends/quick_frontend/quick_blog.py sat_frontends/quick_frontend/quick_chat.py sat_frontends/quick_frontend/quick_contact_list.py sat_frontends/quick_frontend/quick_contact_management.py sat_frontends/quick_frontend/quick_game_tarot.py sat_frontends/quick_frontend/quick_games.py sat_frontends/quick_frontend/quick_list_manager.py sat_frontends/quick_frontend/quick_menus.py sat_frontends/quick_frontend/quick_profile_manager.py sat_frontends/quick_frontend/quick_utils.py sat_frontends/quick_frontend/quick_widgets.py sat_frontends/tools/composition.py sat_frontends/tools/css_color.py sat_frontends/tools/games.py sat_frontends/tools/host_listener.py sat_frontends/tools/jid.py sat_frontends/tools/misc.py sat_frontends/tools/strings.py sat_frontends/tools/xmltools.py sat_frontends/tools/xmlui.py
diffstat 205 files changed, 21261 insertions(+), 10355 deletions(-) [+]
line wrap: on
line diff
--- a/sat/__init__.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/__init__.py	Wed Jun 27 20:14:46 2018 +0200
@@ -18,10 +18,10 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 import os.path
 
-version_file = os.path.join(os.path.dirname(__file__), 'VERSION')
+version_file = os.path.join(os.path.dirname(__file__), "VERSION")
 try:
     with open(version_file) as f:
         __version__ = f.read().strip()
 except NotImplementedError:
     # pyjamas workaround
-    __version__ = '0.7D'
+    __version__ = "0.7D"
--- a/sat/bridge/bridge_constructor/base_constructor.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/bridge/bridge_constructor/base_constructor.py	Wed Jun 27 20:14:46 2018 +0200
@@ -1,5 +1,5 @@
 #!/usr/bin/env python2
-#-*- coding: utf-8 -*-
+# -*- coding: utf-8 -*-
 
 # SàT: a XMPP client
 # Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org)
@@ -29,7 +29,7 @@
 
 
 class ParseError(Exception):
-    #Used when the signature parsing is going wrong (invalid signature ?)
+    # Used when the signature parsing is going wrong (invalid signature ?)
     pass
 
 
@@ -41,7 +41,7 @@
     # replace ##SIGNALS_PART## in template), while the value is the format
     # keys starting with "signal" will be used for signals, while ones starting with
     # "method" will be used for methods
-    # check D-Bus constructor for an example
+    #  check D-Bus constructor for an example
     CORE_FORMATS = None
     CORE_TEMPLATE = None
     CORE_DEST = None
@@ -66,7 +66,7 @@
         @param name: Name of the function to get
         @return: dict, each key has the config value or None if the value is not set"""
         function = {}
-        for option in ['type', 'category', 'sig_in', 'sig_out', 'doc']:
+        for option in ["type", "category", "sig_in", "sig_out", "doc"]:
             try:
                 value = self.bridge_template.get(name, option)
             except NoOptionError:
@@ -87,7 +87,9 @@
                 try:
                     idx = int(match.group(1))
                 except ValueError:
-                    raise ParseError("Invalid value [%s] for parameter number" % match.group(1))
+                    raise ParseError(
+                        "Invalid value [%s] for parameter number" % match.group(1)
+                    )
                 default_dict[idx] = self.bridge_template.get(name, option)
 
         return default_dict
@@ -112,15 +114,17 @@
         option_re = re.compile(r"doc_param_(\d+)")
         value_re = re.compile(r"^(\w+): (.*)$", re.MULTILINE | re.DOTALL)
         for option in self.bridge_template.options(name):
-            if option == 'doc_return':
-                doc_dict['return'] = self.bridge_template.get(name, option)
+            if option == "doc_return":
+                doc_dict["return"] = self.bridge_template.get(name, option)
                 continue
             match = option_re.match(option)
             if match:
                 try:
                     idx = int(match.group(1))
                 except ValueError:
-                    raise ParseError("Invalid value [%s] for parameter number" % match.group(1))
+                    raise ParseError(
+                        "Invalid value [%s] for parameter number" % match.group(1)
+                    )
                 value_match = value_re.match(self.bridge_template.get(name, option))
                 if not value_match:
                     raise ParseError("Invalid value for parameter doc [%i]" % idx)
@@ -141,21 +145,35 @@
         i = 0
 
         while i < len(signature):
-            if signature[i] not in ['b', 'y', 'n', 'i', 'x', 'q', 'u', 't', 'd', 's', 'a']:
+            if signature[i] not in [
+                "b",
+                "y",
+                "n",
+                "i",
+                "x",
+                "q",
+                "u",
+                "t",
+                "d",
+                "s",
+                "a",
+            ]:
                 raise ParseError("Unmanaged attribute type [%c]" % signature[i])
 
-            if signature[i] == 'a':
+            if signature[i] == "a":
                 i += 1
-                if signature[i] != '{' and signature[i] != '(':  # FIXME: must manage tuples out of arrays
+                if (
+                    signature[i] != "{" and signature[i] != "("
+                ):  # FIXME: must manage tuples out of arrays
                     i += 1
                     yield signature[start:i]
                     start = i
                     continue  # we have a simple type for the array
                 opening_car = signature[i]
-                assert(opening_car in ['{', '('])
-                closing_car = '}' if opening_car == '{' else ')'
+                assert opening_car in ["{", "("]
+                closing_car = "}" if opening_car == "{" else ")"
                 opening_count = 1
-                while (True):  # we have a dict or a list of tuples
+                while True:  # we have a dict or a list of tuples
                     i += 1
                     if i >= len(signature):
                         raise ParseError("missing }")
@@ -182,11 +200,19 @@
         attr_string = []
 
         for arg in self.argumentsParser(signature):
-            attr_string.append(("unicode(%(name)s)%(default)s" if (unicode_protect and arg == 's') else "%(name)s%(default)s") % {
-                'name': name[idx][0] if (name and idx in name) else "arg_%i" % idx,
-                'default': "=" + default[idx] if (default and idx in default) else ''})
-                # give arg_1, arg2, etc or name1, name2=default, etc.
-                #give unicode(arg_1), unicode(arg_2), etc. if unicode_protect is set and arg is a string
+            attr_string.append(
+                (
+                    "unicode(%(name)s)%(default)s"
+                    if (unicode_protect and arg == "s")
+                    else "%(name)s%(default)s"
+                )
+                % {
+                    "name": name[idx][0] if (name and idx in name) else "arg_%i" % idx,
+                    "default": "=" + default[idx] if (default and idx in default) else "",
+                }
+            )
+            # give arg_1, arg2, etc or name1, name2=default, etc.
+            # give unicode(arg_1), unicode(arg_2), etc. if unicode_protect is set and arg is a string
             idx += 1
 
         return ", ".join(attr_string)
@@ -214,7 +240,6 @@
         """override this method to extend completion"""
         pass
 
-
     def generate(self, side):
         """generate bridge
 
@@ -242,7 +267,7 @@
         @param side(str): core or frontend
         """
         side_vars = []
-        for var in ('FORMATS', 'TEMPLATE', 'DEST'):
+        for var in ("FORMATS", "TEMPLATE", "DEST"):
             attr = "{}_{}".format(side.upper(), var)
             value = getattr(self, attr)
             if value is None:
@@ -252,59 +277,70 @@
         FORMATS, TEMPLATE, DEST = side_vars
         del side_vars
 
-        parts = {part.upper():[] for part in FORMATS}
+        parts = {part.upper(): [] for part in FORMATS}
         sections = self.bridge_template.sections()
         sections.sort()
         for section in sections:
             function = self.getValues(section)
-            print ("Adding %s %s" % (section, function["type"]))
+            print("Adding %s %s" % (section, function["type"]))
             default = self.getDefault(section)
             arg_doc = self.getArgumentsDoc(section)
             async_ = "async" in self.getFlags(section)
             completion = {
-                'sig_in': function['sig_in'] or '',
-                'sig_out': function['sig_out'] or '',
-                'category': 'plugin' if function['category'] == 'plugin' else 'core',
-                'name': section,
+                "sig_in": function["sig_in"] or "",
+                "sig_out": function["sig_out"] or "",
+                "category": "plugin" if function["category"] == "plugin" else "core",
+                "name": section,
                 # arguments with default values
-                'args': self.getArguments(function['sig_in'], name=arg_doc, default=default),
-                }
+                "args": self.getArguments(
+                    function["sig_in"], name=arg_doc, default=default
+                ),
+            }
 
-            extend_method = getattr(self, "{}_completion_{}".format(side, function["type"]))
+            extend_method = getattr(
+                self, "{}_completion_{}".format(side, function["type"])
+            )
             extend_method(completion, function, default, arg_doc, async_)
 
             for part, fmt in FORMATS.iteritems():
                 if part.startswith(function["type"]):
                     parts[part.upper()].append(fmt.format(**completion))
 
-
-        #at this point, signals_part, methods_part and direct_calls should be filled,
-        #we just have to place them in the right part of the template
+        # at this point, signals_part, methods_part and direct_calls should be filled,
+        # we just have to place them in the right part of the template
         bridge = []
-        const_override = {env[len(C.ENV_OVERRIDE):]:v for env,v in os.environ.iteritems() if env.startswith(C.ENV_OVERRIDE)}
+        const_override = {
+            env[len(C.ENV_OVERRIDE) :]: v
+            for env, v in os.environ.iteritems()
+            if env.startswith(C.ENV_OVERRIDE)
+        }
         template_path = self.getTemplatePath(TEMPLATE)
         try:
             with open(template_path) as template:
                 for line in template:
 
                     for part, extend_list in parts.iteritems():
-                        if line.startswith('##{}_PART##'.format(part)):
+                        if line.startswith("##{}_PART##".format(part)):
                             bridge.extend(extend_list)
                             break
                     else:
                         # the line is not a magic part replacement
-                        if line.startswith('const_'):
-                            const_name = line[len('const_'):line.find(' = ')].strip()
+                        if line.startswith("const_"):
+                            const_name = line[len("const_") : line.find(" = ")].strip()
                             if const_name in const_override:
                                 print("const {} overriden".format(const_name))
-                                bridge.append('const_{} = {}'.format(const_name, const_override[const_name]))
+                                bridge.append(
+                                    "const_{} = {}".format(
+                                        const_name, const_override[const_name]
+                                    )
+                                )
                                 continue
-                        bridge.append(line.replace('\n', ''))
+                        bridge.append(line.replace("\n", ""))
         except IOError:
-            print ("can't open template file [{}]".format(template_path))
+            print("can't open template file [{}]".format(template_path))
             sys.exit(1)
 
-        #now we write to final file
+        # now we write to final file
         self.finalWrite(DEST, bridge)
 
     def finalWrite(self, filename, file_buf):
@@ -314,19 +350,24 @@
         @param file_buf: list of lines (stings) of the file
         """
         if os.path.exists(self.args.dest_dir) and not os.path.isdir(self.args.dest_dir):
-            print ("The destination dir [%s] can't be created: a file with this name already exists !")
+            print(
+                "The destination dir [%s] can't be created: a file with this name already exists !"
+            )
             sys.exit(1)
         try:
             if not os.path.exists(self.args.dest_dir):
                 os.mkdir(self.args.dest_dir)
             full_path = os.path.join(self.args.dest_dir, filename)
             if os.path.exists(full_path) and not self.args.force:
-                print ("The destination file [%s] already exists ! Use --force to overwrite it" % full_path)
+                print(
+                    "The destination file [%s] already exists ! Use --force to overwrite it"
+                    % full_path
+                )
             try:
-                with open(full_path, 'w') as dest_file:
-                    dest_file.write('\n'.join(file_buf))
+                with open(full_path, "w") as dest_file:
+                    dest_file.write("\n".join(file_buf))
             except IOError:
-                print ("Can't open destination file [%s]" % full_path)
+                print("Can't open destination file [%s]" % full_path)
         except OSError:
             print("It's not possible to generate the file, check your permissions")
             exit(1)
--- a/sat/bridge/bridge_constructor/bridge_constructor.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/bridge/bridge_constructor/bridge_constructor.py	Wed Jun 27 20:14:46 2018 +0200
@@ -1,5 +1,5 @@
 #!/usr/bin/env python2
-#-*- coding: utf-8 -*-
+# -*- coding: utf-8 -*-
 
 # SàT: a XMPP client
 # Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org)
@@ -23,23 +23,24 @@
 from sat.bridge.bridge_constructor import constructors, base_constructor
 import argparse
 from ConfigParser import SafeConfigParser as Parser
-from importlib  import import_module
+from importlib import import_module
 import os
 import os.path
 
-#consts
+# consts
 __version__ = C.APP_VERSION
 
 
 class BridgeConstructor(object):
-
     def importConstructors(self):
         constructors_dir = os.path.dirname(constructors.__file__)
         self.protocoles = {}
         for dir_ in os.listdir(constructors_dir):
-            init_path = os.path.join(constructors_dir, dir_, '__init__.py')
-            constructor_path = os.path.join(constructors_dir, dir_, 'constructor.py')
-            module_path = "sat.bridge.bridge_constructor.constructors.{}.constructor".format(dir_)
+            init_path = os.path.join(constructors_dir, dir_, "__init__.py")
+            constructor_path = os.path.join(constructors_dir, dir_, "constructor.py")
+            module_path = "sat.bridge.bridge_constructor.constructors.{}.constructor".format(
+                dir_
+            )
             if os.path.isfile(init_path) and os.path.isfile(constructor_path):
                 mod = import_module(module_path)
                 for attr in dir(mod):
@@ -55,27 +56,66 @@
 
     def parse_args(self):
         """Check command line options"""
-        parser = argparse.ArgumentParser(description=C.DESCRIPTION, formatter_class=argparse.RawDescriptionHelpFormatter)
+        parser = argparse.ArgumentParser(
+            description=C.DESCRIPTION,
+            formatter_class=argparse.RawDescriptionHelpFormatter,
+        )
 
-        parser.add_argument("--version", action="version", version= __version__)
-        default_protocole = C.DEFAULT_PROTOCOLE if C.DEFAULT_PROTOCOLE in self.protocoles else self.protocoles[0]
-        parser.add_argument("-p", "--protocole", choices=sorted(self.protocoles), default=default_protocole,
-            help="generate bridge using PROTOCOLE (default: %(default)s)") # (default: %s, possible values: [%s])" % (DEFAULT_PROTOCOLE, ", ".join(MANAGED_PROTOCOLES)))
-        parser.add_argument("-s", "--side", choices=("core", "frontend"), default="core",
-            help="which side of the bridge do you want to make ?") # (default: %default, possible values: [core, frontend])")
-        default_template = os.path.join(os.path.dirname(bridge_constructor.__file__), 'bridge_template.ini')
-        parser.add_argument("-t", "--template", type=file, default=default_template,
-            help="use TEMPLATE to generate bridge (default: %(default)s)")
-        parser.add_argument("-f", "--force", action="store_true",
-            help=("force overwritting of existing files"))
-        parser.add_argument("-d", "--debug", action="store_true",
-            help=("add debug information printing"))
-        parser.add_argument("--no-unicode", action="store_false", dest="unicode",
-            help=("remove unicode type protection from string results"))
-        parser.add_argument("--flags", nargs='+', default=[],
-            help=("constructors' specific flags"))
-        parser.add_argument("--dest-dir", default=C.DEST_DIR_DEFAULT,
-            help=("directory when the generated files will be written (default: %(default)s)"))
+        parser.add_argument("--version", action="version", version=__version__)
+        default_protocole = (
+            C.DEFAULT_PROTOCOLE
+            if C.DEFAULT_PROTOCOLE in self.protocoles
+            else self.protocoles[0]
+        )
+        parser.add_argument(
+            "-p",
+            "--protocole",
+            choices=sorted(self.protocoles),
+            default=default_protocole,
+            help="generate bridge using PROTOCOLE (default: %(default)s)",
+        )  # (default: %s, possible values: [%s])" % (DEFAULT_PROTOCOLE, ", ".join(MANAGED_PROTOCOLES)))
+        parser.add_argument(
+            "-s",
+            "--side",
+            choices=("core", "frontend"),
+            default="core",
+            help="which side of the bridge do you want to make ?",
+        )  # (default: %default, possible values: [core, frontend])")
+        default_template = os.path.join(
+            os.path.dirname(bridge_constructor.__file__), "bridge_template.ini"
+        )
+        parser.add_argument(
+            "-t",
+            "--template",
+            type=file,
+            default=default_template,
+            help="use TEMPLATE to generate bridge (default: %(default)s)",
+        )
+        parser.add_argument(
+            "-f",
+            "--force",
+            action="store_true",
+            help=("force overwritting of existing files"),
+        )
+        parser.add_argument(
+            "-d", "--debug", action="store_true", help=("add debug information printing")
+        )
+        parser.add_argument(
+            "--no-unicode",
+            action="store_false",
+            dest="unicode",
+            help=("remove unicode type protection from string results"),
+        )
+        parser.add_argument(
+            "--flags", nargs="+", default=[], help=("constructors' specific flags")
+        )
+        parser.add_argument(
+            "--dest-dir",
+            default=C.DEST_DIR_DEFAULT,
+            help=(
+                "directory when the generated files will be written (default: %(default)s)"
+            ),
+        )
 
         return parser.parse_args()
 
@@ -86,7 +126,7 @@
         try:
             template_parser.readfp(args.template)
         except IOError:
-            print ("The template file doesn't exist or is not accessible")
+            print("The template file doesn't exist or is not accessible")
             exit(1)
         constructor = self.protocoles[args.protocole](template_parser, args)
         constructor.generate(args.side)
--- a/sat/bridge/bridge_constructor/constants.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/bridge/bridge_constructor/constants.py	Wed Jun 27 20:14:46 2018 +0200
@@ -1,5 +1,5 @@
 #!/usr/bin/env python2
-#-*- coding: utf-8 -*-
+# -*- coding: utf-8 -*-
 
 # SàT: a XMPP client
 # Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org)
@@ -31,11 +31,13 @@
     This program comes with ABSOLUTELY NO WARRANTY;
     This is free software, and you are welcome to redistribute it
     under certain conditions.
-    """.format(name=NAME, version=constants.Const.APP_VERSION)
-# TODO: move protocoles in separate files (plugins?)
-    DEFAULT_PROTOCOLE = 'dbus'
+    """.format(
+        name=NAME, version=constants.Const.APP_VERSION
+    )
+    #  TODO: move protocoles in separate files (plugins?)
+    DEFAULT_PROTOCOLE = "dbus"
 
-# flags used method/signal declaration (not to be confused with constructor flags)
-    DECLARATION_FLAGS = ['deprecated', 'async']
+    # flags used method/signal declaration (not to be confused with constructor flags)
+    DECLARATION_FLAGS = ["deprecated", "async"]
 
     ENV_OVERRIDE = "SAT_BRIDGE_CONST_"  # Prefix used to override a constant
--- a/sat/bridge/bridge_constructor/constructors/dbus-xml/constructor.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/bridge/bridge_constructor/constructors/dbus-xml/constructor.py	Wed Jun 27 20:14:46 2018 +0200
@@ -1,5 +1,5 @@
 #!/usr/bin/env python2
-#-*- coding: utf-8 -*-
+# -*- coding: utf-8 -*-
 
 # SàT: a XMPP client
 # Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org)
@@ -30,62 +30,73 @@
 
         self.template = "dbus_xml_template.xml"
         self.core_dest = "org.goffi.sat.xml"
-        self.default_annotation = {'a{ss}': 'StringDict',
-                                   'a(sa{ss}as)': 'QList<Contact>',
-                                   'a{i(ss)}': 'HistoryT',
-                                   'a(sss)': 'QList<MenuT>',
-                                   'a{sa{s(sia{ss})}}': 'PresenceStatusT',
-                                   }
+        self.default_annotation = {
+            "a{ss}": "StringDict",
+            "a(sa{ss}as)": "QList<Contact>",
+            "a{i(ss)}": "HistoryT",
+            "a(sss)": "QList<MenuT>",
+            "a{sa{s(sia{ss})}}": "PresenceStatusT",
+        }
 
     def generateCoreSide(self):
         try:
             doc = minidom.parse(self.getTemplatePath(self.template))
-            interface_elt = doc.getElementsByTagName('interface')[0]
+            interface_elt = doc.getElementsByTagName("interface")[0]
         except IOError:
-            print ("Can't access template")
+            print("Can't access template")
             sys.exit(1)
         except IndexError:
-            print ("Template error")
+            print("Template error")
             sys.exit(1)
 
         sections = self.bridge_template.sections()
         sections.sort()
         for section in sections:
             function = self.getValues(section)
-            print ("Adding %s %s" % (section, function["type"]))
-            new_elt = doc.createElement('method' if function["type"] == 'method' else 'signal')
-            new_elt.setAttribute('name', section)
+            print("Adding %s %s" % (section, function["type"]))
+            new_elt = doc.createElement(
+                "method" if function["type"] == "method" else "signal"
+            )
+            new_elt.setAttribute("name", section)
 
             idx = 0
             args_doc = self.getArgumentsDoc(section)
-            for arg in self.argumentsParser(function['sig_in'] or ''):
-                arg_elt = doc.createElement('arg')
-                arg_elt.setAttribute('name', args_doc[idx][0] if idx in args_doc else "arg_%i" % idx)
-                arg_elt.setAttribute('type', arg)
-                _direction = 'in' if function["type"] == 'method' else 'out'
-                arg_elt.setAttribute('direction', _direction)
+            for arg in self.argumentsParser(function["sig_in"] or ""):
+                arg_elt = doc.createElement("arg")
+                arg_elt.setAttribute(
+                    "name", args_doc[idx][0] if idx in args_doc else "arg_%i" % idx
+                )
+                arg_elt.setAttribute("type", arg)
+                _direction = "in" if function["type"] == "method" else "out"
+                arg_elt.setAttribute("direction", _direction)
                 new_elt.appendChild(arg_elt)
                 if "annotation" in self.args.flags:
                     if arg in self.default_annotation:
                         annot_elt = doc.createElement("annotation")
-                        annot_elt.setAttribute('name', "com.trolltech.QtDBus.QtTypeName.In%d" % idx)
-                        annot_elt.setAttribute('value', self.default_annotation[arg])
+                        annot_elt.setAttribute(
+                            "name", "com.trolltech.QtDBus.QtTypeName.In%d" % idx
+                        )
+                        annot_elt.setAttribute("value", self.default_annotation[arg])
                         new_elt.appendChild(annot_elt)
                 idx += 1
 
-            if function['sig_out']:
-                arg_elt = doc.createElement('arg')
-                arg_elt.setAttribute('type', function['sig_out'])
-                arg_elt.setAttribute('direction', 'out')
+            if function["sig_out"]:
+                arg_elt = doc.createElement("arg")
+                arg_elt.setAttribute("type", function["sig_out"])
+                arg_elt.setAttribute("direction", "out")
                 new_elt.appendChild(arg_elt)
                 if "annotation" in self.args.flags:
-                    if function['sig_out'] in self.default_annotation:
+                    if function["sig_out"] in self.default_annotation:
                         annot_elt = doc.createElement("annotation")
-                        annot_elt.setAttribute('name', "com.trolltech.QtDBus.QtTypeName.Out0")
-                        annot_elt.setAttribute('value', self.default_annotation[function['sig_out']])
+                        annot_elt.setAttribute(
+                            "name", "com.trolltech.QtDBus.QtTypeName.Out0"
+                        )
+                        annot_elt.setAttribute(
+                            "value", self.default_annotation[function["sig_out"]]
+                        )
                         new_elt.appendChild(annot_elt)
 
             interface_elt.appendChild(new_elt)
 
-        #now we write to final file
+        # now we write to final file
         self.finalWrite(self.core_dest, [doc.toprettyxml()])
--- a/sat/bridge/bridge_constructor/constructors/dbus/constructor.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/bridge/bridge_constructor/constructors/dbus/constructor.py	Wed Jun 27 20:14:46 2018 +0200
@@ -1,5 +1,5 @@
 #!/usr/bin/env python2
-#-*- coding: utf-8 -*-
+# -*- coding: utf-8 -*-
 
 # SàT: a XMPP client
 # Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org)
@@ -25,73 +25,94 @@
     CORE_TEMPLATE = "dbus_core_template.py"
     CORE_DEST = "dbus_bridge.py"
     CORE_FORMATS = {
-        'signals': """\
+        "signals": """\
     @dbus.service.signal(const_INT_PREFIX+const_{category}_SUFFIX,
                          signature='{sig_in}')
     def {name}(self, {args}):
         {body}\n""",
-
-        'methods': """\
+        "methods": """\
     @dbus.service.method(const_INT_PREFIX+const_{category}_SUFFIX,
                          in_signature='{sig_in}', out_signature='{sig_out}',
                          async_callbacks={async_callbacks})
     def {name}(self, {args}{async_comma}{async_args_def}):
         {debug}return self._callback("{name}", {args_result}{async_comma}{async_args_call})\n""",
-
-        'signal_direct_calls': """\
+        "signal_direct_calls": """\
     def {name}(self, {args}):
         self.dbus_bridge.{name}({args})\n""",
-        }
+    }
 
     FRONTEND_TEMPLATE = "dbus_frontend_template.py"
     FRONTEND_DEST = CORE_DEST
     FRONTEND_FORMATS = {
-        'methods': """\
+        "methods": """\
     def {name}(self, {args}{async_comma}{async_args}):
-        {error_handler}{blocking_call}{debug}return {result}\n""",
-        }
+        {error_handler}{blocking_call}{debug}return {result}\n"""
+    }
 
     def core_completion_signal(self, completion, function, default, arg_doc, async_):
-        completion['category'] = completion['category'].upper()
-        completion['body'] = "pass" if not self.args.debug else 'log.debug ("{}")'.format(completion['name'])
+        completion["category"] = completion["category"].upper()
+        completion["body"] = (
+            "pass"
+            if not self.args.debug
+            else 'log.debug ("{}")'.format(completion["name"])
+        )
 
     def core_completion_method(self, completion, function, default, arg_doc, async_):
-        completion.update({
-            'debug': "" if not self.args.debug else 'log.debug ("%s")\n%s' % (completion['name'], 8 * ' '),
-            'args_result': self.getArguments(function['sig_in'], name=arg_doc, unicode_protect=self.args.unicode),
-            'async_comma': ', ' if async_ and function['sig_in'] else '',
-            'async_args_def': 'callback=None, errback=None' if async_ else '',
-            'async_args_call': 'callback=callback, errback=errback' if async_ else '',
-            'async_callbacks': "('callback', 'errback')" if async_ else "None",
-            'category': completion['category'].upper(),
-            })
+        completion.update(
+            {
+                "debug": ""
+                if not self.args.debug
+                else 'log.debug ("%s")\n%s' % (completion["name"], 8 * " "),
+                "args_result": self.getArguments(
+                    function["sig_in"], name=arg_doc, unicode_protect=self.args.unicode
+                ),
+                "async_comma": ", " if async_ and function["sig_in"] else "",
+                "async_args_def": "callback=None, errback=None" if async_ else "",
+                "async_args_call": "callback=callback, errback=errback" if async_ else "",
+                "async_callbacks": "('callback', 'errback')" if async_ else "None",
+                "category": completion["category"].upper(),
+            }
+        )
 
     def frontend_completion_method(self, completion, function, default, arg_doc, async_):
-        completion.update({
-            # XXX: we can manage blocking call in the same way as async one: if callback is None the call will be blocking
-            'debug': "" if not self.args.debug else 'log.debug ("%s")\n%s' % (completion['name'], 8 * ' '),
-            'args_result': self.getArguments(function['sig_in'], name=arg_doc),
-            'async_args': 'callback=None, errback=None',
-            'async_comma': ', ' if function['sig_in'] else '',
-            'error_handler': """if callback is None:
+        completion.update(
+            {
+                # XXX: we can manage blocking call in the same way as async one: if callback is None the call will be blocking
+                "debug": ""
+                if not self.args.debug
+                else 'log.debug ("%s")\n%s' % (completion["name"], 8 * " "),
+                "args_result": self.getArguments(function["sig_in"], name=arg_doc),
+                "async_args": "callback=None, errback=None",
+                "async_comma": ", " if function["sig_in"] else "",
+                "error_handler": """if callback is None:
             error_handler = None
         else:
             if errback is None:
                 errback = log.error
             error_handler = lambda err:errback(dbus_to_bridge_exception(err))
         """,
-            })
+            }
+        )
         if async_:
-            completion['blocking_call'] = ''
-            completion['async_args_result'] = 'timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler'
+            completion["blocking_call"] = ""
+            completion[
+                "async_args_result"
+            ] = "timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler"
         else:
             # XXX: To have a blocking call, we must have not reply_handler, so we test if callback exists, and add reply_handler only in this case
-            completion['blocking_call'] = """kwargs={}
+            completion[
+                "blocking_call"
+            ] = """kwargs={}
         if callback is not None:
             kwargs['timeout'] = const_TIMEOUT
             kwargs['reply_handler'] = callback
             kwargs['error_handler'] = error_handler
         """
-            completion['async_args_result'] = '**kwargs'
-        result = "self.db_%(category)s_iface.%(name)s(%(args_result)s%(async_comma)s%(async_args_result)s)" % completion
-        completion['result'] = ("unicode(%s)" if self.args.unicode and function['sig_out'] == 's' else "%s") % result
+            completion["async_args_result"] = "**kwargs"
+        result = (
+            "self.db_%(category)s_iface.%(name)s(%(args_result)s%(async_comma)s%(async_args_result)s)"
+            % completion
+        )
+        completion["result"] = (
+            "unicode(%s)" if self.args.unicode and function["sig_out"] == "s" else "%s"
+        ) % result
--- a/sat/bridge/bridge_constructor/constructors/dbus/dbus_core_template.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/bridge/bridge_constructor/constructors/dbus/dbus_core_template.py	Wed Jun 27 20:14:46 2018 +0200
@@ -1,5 +1,5 @@
 #!/usr/bin/env python2
-#-*- coding: utf-8 -*-
+# -*- coding: utf-8 -*-
 
 # SAT: a jabber client
 # Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org)
@@ -23,13 +23,14 @@
 import dbus.mainloop.glib
 import inspect
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from twisted.internet.defer import Deferred
 from sat.core.exceptions import BridgeInitError
 
 const_INT_PREFIX = "org.goffi.SAT"  # Interface prefix
 const_ERROR_PREFIX = const_INT_PREFIX + ".error"
-const_OBJ_PATH = '/org/goffi/SAT/bridge'
+const_OBJ_PATH = "/org/goffi/SAT/bridge"
 const_CORE_SUFFIX = ".core"
 const_PLUGIN_SUFFIX = ".plugin"
 
@@ -73,11 +74,12 @@
                 self.args = (message, twisted_error.value.condition)
             except AttributeError:
                 self.args = (message,)
-        self._dbus_error_name = '.'.join([const_ERROR_PREFIX, class_.__module__, class_.__name__])
+        self._dbus_error_name = ".".join(
+            [const_ERROR_PREFIX, class_.__module__, class_.__name__]
+        )
 
 
 class DbusObject(dbus.service.Object):
-
     def __init__(self, bus, path):
         dbus.service.Object.__init__(self, bus, path)
         log.debug("Init DbusObject...")
@@ -93,7 +95,7 @@
             raise MethodNotRegistered
 
         if "callback" in kwargs:
-            #we must have errback too
+            # we must have errback too
             if not "errback" in kwargs:
                 log.error("errback is missing in method call [%s]" % name)
                 raise InternalError
@@ -107,27 +109,29 @@
             if not isinstance(result, Deferred):
                 log.error("Asynchronous method [%s] does not return a Deferred." % name)
                 raise AsyncNotDeferred
-            result.addCallback(lambda result: callback() if result is None else callback(result))
+            result.addCallback(
+                lambda result: callback() if result is None else callback(result)
+            )
             result.addErrback(lambda err: errback(GenericException(err)))
         else:
             if isinstance(result, Deferred):
                 log.error("Synchronous method [%s] return a Deferred." % name)
                 raise DeferredNotAsync
             return result
+
     ### signals ###
 
-    @dbus.service.signal(const_INT_PREFIX + const_PLUGIN_SUFFIX,
-                         signature='')
+    @dbus.service.signal(const_INT_PREFIX + const_PLUGIN_SUFFIX, signature="")
     def dummySignal(self):
-        #FIXME: workaround for addSignal (doesn't work if one method doensn't
+        # FIXME: workaround for addSignal (doesn't work if one method doensn't
         #       already exist for plugins), probably missing some initialisation, need
         #       further investigations
         pass
 
-##SIGNALS_PART##
+    ##SIGNALS_PART##
     ### methods ###
 
-##METHODS_PART##
+    ##METHODS_PART##
     def __attributes(self, in_sign):
         """Return arguments to user given a in_sign
         @param in_sign: in_sign in the short form (using s,a,i,b etc)
@@ -136,22 +140,24 @@
         idx = 0
         attr = []
         while i < len(in_sign):
-            if in_sign[i] not in ['b', 'y', 'n', 'i', 'x', 'q', 'u', 't', 'd', 's', 'a']:
+            if in_sign[i] not in ["b", "y", "n", "i", "x", "q", "u", "t", "d", "s", "a"]:
                 raise ParseError("Unmanaged attribute type [%c]" % in_sign[i])
 
             attr.append("arg_%i" % idx)
             idx += 1
 
-            if in_sign[i] == 'a':
+            if in_sign[i] == "a":
                 i += 1
-                if in_sign[i] != '{' and in_sign[i] != '(':  # FIXME: must manage tuples out of arrays
+                if (
+                    in_sign[i] != "{" and in_sign[i] != "("
+                ):  # FIXME: must manage tuples out of arrays
                     i += 1
                     continue  # we have a simple type for the array
                 opening_car = in_sign[i]
-                assert(opening_car in ['{', '('])
-                closing_car = '}' if opening_car == '{' else ')'
+                assert opening_car in ["{", "("]
+                closing_car = "}" if opening_car == "{" else ")"
                 opening_count = 1
-                while (True):  # we have a dict or a list of tuples
+                while True:  # we have a dict or a list of tuples
                     i += 1
                     if i >= len(in_sign):
                         raise ParseError("missing }")
@@ -172,47 +178,80 @@
         _defaults = list(inspect_args.defaults or [])
 
         if inspect.ismethod(method):
-            #if we have a method, we don't want the first argument (usually 'self')
-            del(_arguments[0])
+            # if we have a method, we don't want the first argument (usually 'self')
+            del (_arguments[0])
 
-        #first arguments are for the _callback method
-        arguments_callback = ', '.join([repr(name)] + ((_arguments + ['callback=callback', 'errback=errback']) if async else _arguments))
+        # first arguments are for the _callback method
+        arguments_callback = ", ".join(
+            [repr(name)]
+            + (
+                (_arguments + ["callback=callback", "errback=errback"])
+                if async
+                else _arguments
+            )
+        )
 
         if async:
-            _arguments.extend(['callback', 'errback'])
+            _arguments.extend(["callback", "errback"])
             _defaults.extend([None, None])
 
-        #now we create a second list with default values
+        # now we create a second list with default values
         for i in range(1, len(_defaults) + 1):
             _arguments[-i] = "%s = %s" % (_arguments[-i], repr(_defaults[-i]))
 
-        arguments_defaults = ', '.join(_arguments)
+        arguments_defaults = ", ".join(_arguments)
 
-        code = compile('def %(name)s (self,%(arguments_defaults)s): return self._callback(%(arguments_callback)s)' %
-                       {'name': name, 'arguments_defaults': arguments_defaults, 'arguments_callback': arguments_callback}, '<DBus bridge>', 'exec')
-        exec (code)  # FIXME: to the same thing in a cleaner way, without compile/exec
+        code = compile(
+            "def %(name)s (self,%(arguments_defaults)s): return self._callback(%(arguments_callback)s)"
+            % {
+                "name": name,
+                "arguments_defaults": arguments_defaults,
+                "arguments_callback": arguments_callback,
+            },
+            "<DBus bridge>",
+            "exec",
+        )
+        exec(code)  # FIXME: to the same thing in a cleaner way, without compile/exec
         method = locals()[name]
-        async_callbacks = ('callback', 'errback') if async else None
-        setattr(DbusObject, name, dbus.service.method(
-            const_INT_PREFIX + int_suffix, in_signature=in_sign, out_signature=out_sign,
-            async_callbacks=async_callbacks)(method))
+        async_callbacks = ("callback", "errback") if async else None
+        setattr(
+            DbusObject,
+            name,
+            dbus.service.method(
+                const_INT_PREFIX + int_suffix,
+                in_signature=in_sign,
+                out_signature=out_sign,
+                async_callbacks=async_callbacks,
+            )(method),
+        )
         function = getattr(self, name)
-        func_table = self._dbus_class_table[self.__class__.__module__ + '.' + self.__class__.__name__][function._dbus_interface]
+        func_table = self._dbus_class_table[
+            self.__class__.__module__ + "." + self.__class__.__name__
+        ][function._dbus_interface]
         func_table[function.__name__] = function  # Needed for introspection
 
     def addSignal(self, name, int_suffix, signature, doc={}):
         """Dynamically add a signal to Dbus Bridge"""
-        attributes = ', '.join(self.__attributes(signature))
-        #TODO: use doc parameter to name attributes
+        attributes = ", ".join(self.__attributes(signature))
+        # TODO: use doc parameter to name attributes
 
-        #code = compile ('def '+name+' (self,'+attributes+'): log.debug ("'+name+' signal")', '<DBus bridge>','exec') #XXX: the log.debug is too annoying with xmllog
-        code = compile('def ' + name + ' (self,' + attributes + '): pass', '<DBus bridge>', 'exec')
-        exec (code)
+        # code = compile ('def '+name+' (self,'+attributes+'): log.debug ("'+name+' signal")', '<DBus bridge>','exec') #XXX: the log.debug is too annoying with xmllog
+        code = compile(
+            "def " + name + " (self," + attributes + "): pass", "<DBus bridge>", "exec"
+        )
+        exec(code)
         signal = locals()[name]
-        setattr(DbusObject, name, dbus.service.signal(
-            const_INT_PREFIX + int_suffix, signature=signature)(signal))
+        setattr(
+            DbusObject,
+            name,
+            dbus.service.signal(const_INT_PREFIX + int_suffix, signature=signature)(
+                signal
+            ),
+        )
         function = getattr(self, name)
-        func_table = self._dbus_class_table[self.__class__.__module__ + '.' + self.__class__.__name__][function._dbus_interface]
+        func_table = self._dbus_class_table[
+            self.__class__.__module__ + "." + self.__class__.__name__
+        ][function._dbus_interface]
         func_table[function.__name__] = function  # Needed for introspection
 
 
@@ -223,20 +262,24 @@
         try:
             self.session_bus = dbus.SessionBus()
         except dbus.DBusException as e:
-            if e._dbus_error_name == 'org.freedesktop.DBus.Error.NotSupported':
-                log.error(_(u"D-Bus is not launched, please see README to see instructions on how to launch it"))
+            if e._dbus_error_name == "org.freedesktop.DBus.Error.NotSupported":
+                log.error(
+                    _(
+                        u"D-Bus is not launched, please see README to see instructions on how to launch it"
+                    )
+                )
             raise BridgeInitError
         self.dbus_name = dbus.service.BusName(const_INT_PREFIX, self.session_bus)
         self.dbus_bridge = DbusObject(self.session_bus, const_OBJ_PATH)
 
-##SIGNAL_DIRECT_CALLS_PART##
+    ##SIGNAL_DIRECT_CALLS_PART##
     def register_method(self, name, callback):
         log.debug("registering DBus bridge method [%s]" % name)
         self.dbus_bridge.register_method(name, callback)
 
     def addMethod(self, name, int_suffix, in_sign, out_sign, method, async=False, doc={}):
         """Dynamically add a method to Dbus Bridge"""
-        #FIXME: doc parameter is kept only temporary, the time to remove it from calls
+        # FIXME: doc parameter is kept only temporary, the time to remove it from calls
         log.debug("Adding method [%s] to DBus bridge" % name)
         self.dbus_bridge.addMethod(name, int_suffix, in_sign, out_sign, method, async)
         self.register_method(name, method)
--- a/sat/bridge/bridge_constructor/constructors/embedded/constructor.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/bridge/bridge_constructor/constructors/embedded/constructor.py	Wed Jun 27 20:14:46 2018 +0200
@@ -1,5 +1,5 @@
 #!/usr/bin/env python2
-#-*- coding: utf-8 -*-
+# -*- coding: utf-8 -*-
 
 # SàT: a XMPP client
 # Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org)
@@ -18,7 +18,8 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 from sat.bridge.bridge_constructor import base_constructor
-# from textwraps import dedent
+
+#  from textwraps import dedent
 
 
 class EmbeddedConstructor(base_constructor.Constructor):
@@ -26,11 +27,11 @@
     CORE_TEMPLATE = "embedded_template.py"
     CORE_DEST = "embedded.py"
     CORE_FORMATS = {
-        'methods': """\
+        "methods": """\
     def {name}(self, {args}{args_comma}callback=None, errback=None):
 {ret_routine}
 """,
-        'signals': """\
+        "signals": """\
     def {name}(self, {args}):
         try:
             cb = self._signals_cbs["{category}"]["{name}"]
@@ -38,22 +39,30 @@
             log.warning(u"ignoring signal {name}: no callback registered")
         else:
             cb({args_result})
-"""
-        }
+""",
+    }
     FRONTEND_TEMPLATE = "embedded_frontend_template.py"
     FRONTEND_DEST = CORE_DEST
     FRONTEND_FORMATS = {}
 
     def core_completion_method(self, completion, function, default, arg_doc, async_):
-        completion.update({
-            'debug': "" if not self.args.debug else 'log.debug ("%s")\n%s' % (completion['name'], 8 * ' '),
-            'args_result': self.getArguments(function['sig_in'], name=arg_doc),
-            'args_comma': ', ' if function['sig_in'] else '',
-            })
+        completion.update(
+            {
+                "debug": ""
+                if not self.args.debug
+                else 'log.debug ("%s")\n%s' % (completion["name"], 8 * " "),
+                "args_result": self.getArguments(function["sig_in"], name=arg_doc),
+                "args_comma": ", " if function["sig_in"] else "",
+            }
+        )
 
         if async_:
-            completion["cb_or_lambda"] = "callback" if function['sig_out'] else "lambda dummy: callback()"
-            completion["ret_routine"] = """\
+            completion["cb_or_lambda"] = (
+                "callback" if function["sig_out"] else "lambda dummy: callback()"
+            )
+            completion[
+                "ret_routine"
+            ] = """\
         d = self._methods_cbs["{name}"]({args_result})
         if callback is not None:
             d.addCallback({cb_or_lambda})
@@ -62,10 +71,14 @@
         else:
             d.addErrback(errback)
         return d
-        """.format(**completion)
+        """.format(
+                **completion
+            )
         else:
-            completion['ret_or_nothing'] = 'ret' if function['sig_out'] else ''
-            completion["ret_routine"] = """\
+            completion["ret_or_nothing"] = "ret" if function["sig_out"] else ""
+            completion[
+                "ret_routine"
+            ] = """\
         try:
             ret = self._methods_cbs["{name}"]({args_result})
         except Exception as e:
@@ -77,9 +90,11 @@
             if callback is None:
                 return ret
             else:
-                callback({ret_or_nothing})""".format(**completion)
+                callback({ret_or_nothing})""".format(
+                **completion
+            )
 
     def core_completion_signal(self, completion, function, default, arg_doc, async_):
-        completion.update({
-            'args_result': self.getArguments(function['sig_in'], name=arg_doc),
-            })
+        completion.update(
+            {"args_result": self.getArguments(function["sig_in"], name=arg_doc)}
+        )
--- a/sat/bridge/bridge_constructor/constructors/embedded/embedded_frontend_template.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/bridge/bridge_constructor/constructors/embedded/embedded_frontend_template.py	Wed Jun 27 20:14:46 2018 +0200
@@ -1,5 +1,5 @@
 #!/usr/bin/env python2
-#-*- coding: utf-8 -*-
+# -*- coding: utf-8 -*-
 
 # SàT: a XMPP client
 # Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org)
--- a/sat/bridge/bridge_constructor/constructors/embedded/embedded_template.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/bridge/bridge_constructor/constructors/embedded/embedded_template.py	Wed Jun 27 20:14:46 2018 +0200
@@ -1,5 +1,5 @@
 #!/usr/bin/env python2
-#-*- coding: utf-8 -*-
+# -*- coding: utf-8 -*-
 
 # SàT: a XMPP client
 # Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org)
@@ -18,6 +18,7 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from sat.core import exceptions
 
@@ -26,10 +27,7 @@
     def __init__(self):
         log.debug(u"Init embedded bridge...")
         self._methods_cbs = {}
-        self._signals_cbs = {
-            "core": {},
-            "plugin": {}
-            }
+        self._signals_cbs = {"core": {}, "plugin": {}}
 
     def bridgeConnect(self, callback, errback):
         callback()
@@ -43,7 +41,11 @@
     def register_signal(self, functionName, handler, iface="core"):
         iface_dict = self._signals_cbs[iface]
         if functionName in iface_dict:
-            raise exceptions.ConflictError(u"signal {name} is already regitered for interface {iface}".format(name=functionName, iface=iface))
+            raise exceptions.ConflictError(
+                u"signal {name} is already regitered for interface {iface}".format(
+                    name=functionName, iface=iface
+                )
+            )
         iface_dict[functionName] = handler
 
     def call_method(self, name, out_sign, async_, args, kwargs):
@@ -84,23 +86,36 @@
             cb(*args, **kwargs)
 
     def addMethod(self, name, int_suffix, in_sign, out_sign, method, async=False, doc={}):
-        #FIXME: doc parameter is kept only temporary, the time to remove it from calls
+        # FIXME: doc parameter is kept only temporary, the time to remove it from calls
         log.debug("Adding method [{}] to embedded bridge".format(name))
         self.register_method(name, method)
-        setattr(self.__class__, name, lambda self_, *args, **kwargs: self.call_method(name, out_sign, async, args, kwargs))
+        setattr(
+            self.__class__,
+            name,
+            lambda self_, *args, **kwargs: self.call_method(
+                name, out_sign, async, args, kwargs
+            ),
+        )
 
     def addSignal(self, name, int_suffix, signature, doc={}):
-        setattr(self.__class__, name, lambda self_, *args, **kwargs: self.send_signal(name, args, kwargs))
+        setattr(
+            self.__class__,
+            name,
+            lambda self_, *args, **kwargs: self.send_signal(name, args, kwargs),
+        )
 
     ## signals ##
 
+
 ##SIGNALS_PART##
-    ## methods ##
+## methods ##
 
 ##METHODS_PART##
 
 # we want the same instance for both core and frontend
 bridge = None
+
+
 def Bridge():
     global bridge
     if bridge is None:
--- a/sat/bridge/bridge_constructor/constructors/mediawiki/constructor.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/bridge/bridge_constructor/constructors/mediawiki/constructor.py	Wed Jun 27 20:14:46 2018 +0200
@@ -1,5 +1,5 @@
 #!/usr/bin/env python2
-#-*- coding: utf-8 -*-
+# -*- coding: utf-8 -*-
 
 # SàT: a XMPP client
 # Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org)
@@ -24,7 +24,6 @@
 
 
 class MediawikiConstructor(base_constructor.Constructor):
-
     def __init__(self, bridge_template, options):
         base_constructor.Constructor.__init__(self, bridge_template, options)
         self.core_template = "mediawiki_template.tpl"
@@ -35,10 +34,10 @@
 
         def anchor_link(match):
             link = match.group(1)
-            #we add anchor_link for [method_name] syntax:
+            # we add anchor_link for [method_name] syntax:
             if link in self.bridge_template.sections():
                 return "[[#%s|%s]]" % (link, link)
-            print ("WARNING: found an anchor link to an unknown method")
+            print("WARNING: found an anchor link to an unknown method")
             return link
 
         return re.sub(r"\[(\w+)\]", anchor_link, text)
@@ -51,12 +50,12 @@
         arg_doc = self.getArgumentsDoc(name)
         arg_default = self.getDefault(name)
         args_str = self.getArguments(sig_in)
-        args = args_str.split(', ') if args_str else []  # ugly but it works :)
+        args = args_str.split(", ") if args_str else []  # ugly but it works :)
         wiki = []
         for i in range(len(args)):
             if i in arg_doc:
                 name, doc = arg_doc[i]
-                doc = '\n:'.join(doc.rstrip('\n').split('\n'))
+                doc = "\n:".join(doc.rstrip("\n").split("\n"))
                 wiki.append("; %s: %s" % (name, self._addTextDecorations(doc)))
             else:
                 wiki.append("; arg_%d: " % i)
@@ -70,9 +69,13 @@
         """
         arg_doc = self.getArgumentsDoc(name)
         wiki = []
-        if 'return' in arg_doc:
-            wiki.append('\n|-\n! scope=row | return value\n|')
-            wiki.append('<br />\n'.join(self._addTextDecorations(arg_doc['return']).rstrip('\n').split('\n')))
+        if "return" in arg_doc:
+            wiki.append("\n|-\n! scope=row | return value\n|")
+            wiki.append(
+                "<br />\n".join(
+                    self._addTextDecorations(arg_doc["return"]).rstrip("\n").split("\n")
+                )
+            )
         return "\n".join(wiki)
 
     def generateCoreSide(self):
@@ -82,37 +85,49 @@
         sections.sort()
         for section in sections:
             function = self.getValues(section)
-            print ("Adding %s %s" % (section, function["type"]))
+            print("Adding %s %s" % (section, function["type"]))
             async_msg = """<br />'''This method is asynchronous'''"""
             deprecated_msg = """<br />'''<font color="#FF0000">/!\ WARNING /!\ : This method is deprecated, please don't use it !</font>'''"""
-            signature_signal = \
-            """\
+            signature_signal = (
+                """\
 ! scope=row | signature
 | %s
 |-\
-""" % function['sig_in']
-            signature_method = \
-            """\
+"""
+                % function["sig_in"]
+            )
+            signature_method = """\
 ! scope=row | signature in
 | %s
 |-
 ! scope=row | signature out
 | %s
 |-\
-""" % (function['sig_in'], function['sig_out'])
+""" % (
+                function["sig_in"],
+                function["sig_out"],
+            )
             completion = {
-                'signature': signature_signal if function['type'] == "signal" else signature_method,
-                'sig_out': function['sig_out'] or '',
-                'category': function['category'],
-                'name': section,
-                'doc': self.getDoc(section) or "FIXME: No description available",
-                'async': async_msg if "async" in self.getFlags(section) else "",
-                'deprecated': deprecated_msg if "deprecated" in self.getFlags(section) else "",
-                'parameters': self._wikiParameter(section, function['sig_in']),
-                'return': self._wikiReturn(section) if function['type'] == 'method' else ''}
+                "signature": signature_signal
+                if function["type"] == "signal"
+                else signature_method,
+                "sig_out": function["sig_out"] or "",
+                "category": function["category"],
+                "name": section,
+                "doc": self.getDoc(section) or "FIXME: No description available",
+                "async": async_msg if "async" in self.getFlags(section) else "",
+                "deprecated": deprecated_msg
+                if "deprecated" in self.getFlags(section)
+                else "",
+                "parameters": self._wikiParameter(section, function["sig_in"]),
+                "return": self._wikiReturn(section)
+                if function["type"] == "method"
+                else "",
+            }
 
-            dest = signals_part if function['type'] == "signal" else methods_part
-            dest.append("""\
+            dest = signals_part if function["type"] == "signal" else methods_part
+            dest.append(
+                """\
 == %(name)s ==
 ''%(doc)s''
 %(deprecated)s
@@ -126,28 +141,28 @@
 |
 %(parameters)s%(return)s
 |}
-""" % completion)
+"""
+                % completion
+            )
 
-        #at this point, signals_part, and methods_part should be filled,
-        #we just have to place them in the right part of the template
+        # at this point, signals_part, and methods_part should be filled,
+        # we just have to place them in the right part of the template
         core_bridge = []
         template_path = self.getTemplatePath(self.core_template)
         try:
             with open(template_path) as core_template:
                 for line in core_template:
-                    if line.startswith('##SIGNALS_PART##'):
+                    if line.startswith("##SIGNALS_PART##"):
                         core_bridge.extend(signals_part)
-                    elif line.startswith('##METHODS_PART##'):
+                    elif line.startswith("##METHODS_PART##"):
                         core_bridge.extend(methods_part)
-                    elif line.startswith('##TIMESTAMP##'):
-                        core_bridge.append('Generated on %s' % datetime.now())
+                    elif line.startswith("##TIMESTAMP##"):
+                        core_bridge.append("Generated on %s" % datetime.now())
                     else:
-                        core_bridge.append(line.replace('\n', ''))
+                        core_bridge.append(line.replace("\n", ""))
         except IOError:
-            print ("Can't open template file [%s]" % template_path)
+            print("Can't open template file [%s]" % template_path)
             sys.exit(1)
 
-        #now we write to final file
+        # now we write to final file
         self.finalWrite(self.core_dest, core_bridge)
-
-
--- a/sat/bridge/bridge_constructor/constructors/pb/constructor.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/bridge/bridge_constructor/constructors/pb/constructor.py	Wed Jun 27 20:14:46 2018 +0200
@@ -1,5 +1,5 @@
 #!/usr/bin/env python2
-#-*- coding: utf-8 -*-
+# -*- coding: utf-8 -*-
 
 # SàT: a XMPP client
 # Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org)
@@ -25,32 +25,42 @@
     CORE_TEMPLATE = "pb_core_template.py"
     CORE_DEST = "pb.py"
     CORE_FORMATS = {
-        'signals': """\
+        "signals": """\
     def {name}(self, {args}):
-        {debug}self.sendSignal("{name}", {args_no_def})\n""",
-        }
+        {debug}self.sendSignal("{name}", {args_no_def})\n"""
+    }
 
     FRONTEND_TEMPLATE = "pb_frontend_template.py"
     FRONTEND_DEST = CORE_DEST
     FRONTEND_FORMATS = {
-        'methods': """\
+        "methods": """\
     def {name}(self{args_comma}{args}, callback=None, errback=None):
         {debug}d = self.root.callRemote("{name}"{args_comma}{args_no_def})
         if callback is not None:
             d.addCallback({callback})
         if errback is None:
             errback = self._generic_errback
-        d.addErrback(errback)\n""",
-        }
+        d.addErrback(errback)\n"""
+    }
 
     def core_completion_signal(self, completion, function, default, arg_doc, async_):
-        completion['args_no_def'] = self.getArguments(function['sig_in'], name=arg_doc)
-        completion['debug'] = "" if not self.args.debug else 'log.debug ("%s")\n%s' % (completion['name'], 8 * ' ')
+        completion["args_no_def"] = self.getArguments(function["sig_in"], name=arg_doc)
+        completion["debug"] = (
+            ""
+            if not self.args.debug
+            else 'log.debug ("%s")\n%s' % (completion["name"], 8 * " ")
+        )
 
     def frontend_completion_method(self, completion, function, default, arg_doc, async_):
-        completion.update({
-            'args_comma': ', ' if function['sig_in'] else '',
-            'args_no_def': self.getArguments(function['sig_in'], name=arg_doc),
-            'callback': 'callback' if function['sig_out'] else 'lambda dummy: callback()',
-            'debug': "" if not self.args.debug else 'log.debug ("%s")\n%s' % (completion['name'], 8 * ' '),
-            })
+        completion.update(
+            {
+                "args_comma": ", " if function["sig_in"] else "",
+                "args_no_def": self.getArguments(function["sig_in"], name=arg_doc),
+                "callback": "callback"
+                if function["sig_out"]
+                else "lambda dummy: callback()",
+                "debug": ""
+                if not self.args.debug
+                else 'log.debug ("%s")\n%s' % (completion["name"], 8 * " "),
+            }
+        )
--- a/sat/bridge/bridge_constructor/constructors/pb/pb_core_template.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/bridge/bridge_constructor/constructors/pb/pb_core_template.py	Wed Jun 27 20:14:46 2018 +0200
@@ -1,5 +1,5 @@
 #!/usr/bin/env python2
-#-*- coding: utf-8 -*-
+# -*- coding: utf-8 -*-
 
 # SAT: a jabber client
 # Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org)
@@ -19,6 +19,7 @@
 
 
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from twisted.spread import jelly, pb
 from twisted.internet import reactor
@@ -28,17 +29,18 @@
 # we monkey patch jelly to handle namedtuple
 ori_jelly = jelly._Jellier.jelly
 
+
 def fixed_jelly(self, obj):
     """this method fix handling of namedtuple"""
     if isinstance(obj, tuple) and not obj is tuple:
         obj = tuple(obj)
     return ori_jelly(self, obj)
 
+
 jelly._Jellier.jelly = fixed_jelly
 
 
 class PBRoot(pb.Root):
-
     def __init__(self):
         self.signals_handlers = []
 
@@ -47,10 +49,11 @@
         log.info(u"registered signal handler")
 
     def sendSignalEb(self, failure, signal_name):
-        log.error(u"Error while sending signal {name}: {msg}".format(
-            name = signal_name,
-            msg = failure,
-            ))
+        log.error(
+            u"Error while sending signal {name}: {msg}".format(
+                name=signal_name, msg=failure
+            )
+        )
 
     def sendSignal(self, name, args, kwargs):
         to_remove = []
@@ -66,11 +69,11 @@
                 log.debug(u"Removing signal handler for dead frontend")
                 self.signals_handlers.remove(handler)
 
+
 ##METHODS_PART##
 
 
 class Bridge(object):
-
     def __init__(self):
         log.info("Init Perspective Broker...")
         self.root = PBRoot()
@@ -85,17 +88,20 @@
 
     def register_method(self, name, callback):
         log.debug("registering PB bridge method [%s]" % name)
-        setattr(self.root, "remote_"+name, callback)
-        # self.root.register_method(name, callback)
+        setattr(self.root, "remote_" + name, callback)
+        #  self.root.register_method(name, callback)
 
     def addMethod(self, name, int_suffix, in_sign, out_sign, method, async=False, doc={}):
         """Dynamically add a method to PB Bridge"""
-        #FIXME: doc parameter is kept only temporary, the time to remove it from calls
+        # FIXME: doc parameter is kept only temporary, the time to remove it from calls
         log.debug("Adding method {name} to PB bridge".format(name=name))
         self.register_method(name, method)
 
     def addSignal(self, name, int_suffix, signature, doc={}):
         log.debug("Adding signal {name} to PB bridge".format(name=name))
-        setattr(self, name, lambda *args, **kwargs: self.sendSignal(name, *args, **kwargs))
+        setattr(
+            self, name, lambda *args, **kwargs: self.sendSignal(name, *args, **kwargs)
+        )
+
 
 ##SIGNALS_PART##
--- a/sat/bridge/bridge_constructor/constructors/pb/pb_frontend_template.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/bridge/bridge_constructor/constructors/pb/pb_frontend_template.py	Wed Jun 27 20:14:46 2018 +0200
@@ -1,5 +1,5 @@
 #!/usr/bin/env python2
-#-*- coding: utf-8 -*-
+# -*- coding: utf-8 -*-
 
 # SAT communication bridge
 # Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org)
@@ -18,6 +18,7 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from sat.core import exceptions
 from twisted.spread import pb
@@ -25,11 +26,9 @@
 
 
 class SignalsHandler(pb.Referenceable):
-
     def __getattr__(self, name):
         if name.startswith("remote_"):
-            log.debug(u"calling an unregistered signal: {name}".format(
-                name = name[7:]))
+            log.debug(u"calling an unregistered signal: {name}".format(name=name[7:]))
             return lambda *args, **kwargs: None
 
         else:
@@ -43,13 +42,15 @@
         except AttributeError:
             pass
         else:
-            raise exceptions.InternalError(u"{name} signal handler has been registered twice".format(
-                name = method_name))
+            raise exceptions.InternalError(
+                u"{name} signal handler has been registered twice".format(
+                    name=method_name
+                )
+            )
         setattr(self, method_name, handler)
 
 
 class Bridge(object):
-
     def __init__(self):
         self.signals_handler = SignalsHandler()
 
@@ -81,11 +82,11 @@
         callback = errback = None
         if kwargs:
             try:
-                callback = kwargs.pop('callback')
+                callback = kwargs.pop("callback")
             except KeyError:
                 pass
             try:
-                errback = kwargs.pop('errback')
+                errback = kwargs.pop("errback")
             except KeyError:
                 pass
         elif len(args) >= 2 and callable(args[-1]) and callable(args[-2]):
@@ -124,4 +125,5 @@
     def register_signal(self, functionName, handler, iface="core"):
         self.signals_handler.register_signal(functionName, handler, iface)
 
+
 ##METHODS_PART##
--- a/sat/bridge/dbus_bridge.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/bridge/dbus_bridge.py	Wed Jun 27 20:14:46 2018 +0200
@@ -1,5 +1,5 @@
 #!/usr/bin/env python2
-#-*- coding: utf-8 -*-
+# -*- coding: utf-8 -*-
 
 # SAT: a jabber client
 # Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org)
@@ -23,13 +23,14 @@
 import dbus.mainloop.glib
 import inspect
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from twisted.internet.defer import Deferred
 from sat.core.exceptions import BridgeInitError
 
 const_INT_PREFIX = "org.goffi.SAT"  # Interface prefix
 const_ERROR_PREFIX = const_INT_PREFIX + ".error"
-const_OBJ_PATH = '/org/goffi/SAT/bridge'
+const_OBJ_PATH = "/org/goffi/SAT/bridge"
 const_CORE_SUFFIX = ".core"
 const_PLUGIN_SUFFIX = ".plugin"
 
@@ -73,11 +74,12 @@
                 self.args = (message, twisted_error.value.condition)
             except AttributeError:
                 self.args = (message,)
-        self._dbus_error_name = '.'.join([const_ERROR_PREFIX, class_.__module__, class_.__name__])
+        self._dbus_error_name = ".".join(
+            [const_ERROR_PREFIX, class_.__module__, class_.__name__]
+        )
 
 
 class DbusObject(dbus.service.Object):
-
     def __init__(self, bus, path):
         dbus.service.Object.__init__(self, bus, path)
         log.debug("Init DbusObject...")
@@ -93,7 +95,7 @@
             raise MethodNotRegistered
 
         if "callback" in kwargs:
-            #we must have errback too
+            # we must have errback too
             if not "errback" in kwargs:
                 log.error("errback is missing in method call [%s]" % name)
                 raise InternalError
@@ -107,389 +109,798 @@
             if not isinstance(result, Deferred):
                 log.error("Asynchronous method [%s] does not return a Deferred." % name)
                 raise AsyncNotDeferred
-            result.addCallback(lambda result: callback() if result is None else callback(result))
+            result.addCallback(
+                lambda result: callback() if result is None else callback(result)
+            )
             result.addErrback(lambda err: errback(GenericException(err)))
         else:
             if isinstance(result, Deferred):
                 log.error("Synchronous method [%s] return a Deferred." % name)
                 raise DeferredNotAsync
             return result
+
     ### signals ###
 
-    @dbus.service.signal(const_INT_PREFIX + const_PLUGIN_SUFFIX,
-                         signature='')
+    @dbus.service.signal(const_INT_PREFIX + const_PLUGIN_SUFFIX, signature="")
     def dummySignal(self):
-        #FIXME: workaround for addSignal (doesn't work if one method doensn't
+        # FIXME: workaround for addSignal (doesn't work if one method doensn't
         #       already exist for plugins), probably missing some initialisation, need
         #       further investigations
         pass
 
-    @dbus.service.signal(const_INT_PREFIX+const_CORE_SUFFIX,
-                         signature='a{ss}sis')
+    @dbus.service.signal(const_INT_PREFIX + const_CORE_SUFFIX, signature="a{ss}sis")
     def actionNew(self, action_data, id, security_limit, profile):
         pass
 
-    @dbus.service.signal(const_INT_PREFIX+const_CORE_SUFFIX,
-                         signature='ss')
+    @dbus.service.signal(const_INT_PREFIX + const_CORE_SUFFIX, signature="ss")
     def connected(self, profile, jid_s):
         pass
 
-    @dbus.service.signal(const_INT_PREFIX+const_CORE_SUFFIX,
-                         signature='ss')
+    @dbus.service.signal(const_INT_PREFIX + const_CORE_SUFFIX, signature="ss")
     def contactDeleted(self, entity_jid, profile):
         pass
 
-    @dbus.service.signal(const_INT_PREFIX+const_CORE_SUFFIX,
-                         signature='s')
+    @dbus.service.signal(const_INT_PREFIX + const_CORE_SUFFIX, signature="s")
     def disconnected(self, profile):
         pass
 
-    @dbus.service.signal(const_INT_PREFIX+const_CORE_SUFFIX,
-                         signature='ssss')
+    @dbus.service.signal(const_INT_PREFIX + const_CORE_SUFFIX, signature="ssss")
     def entityDataUpdated(self, jid, name, value, profile):
         pass
 
-    @dbus.service.signal(const_INT_PREFIX+const_CORE_SUFFIX,
-                         signature='sdssa{ss}a{ss}sa{ss}s')
-    def messageNew(self, uid, timestamp, from_jid, to_jid, message, subject, mess_type, extra, profile):
+    @dbus.service.signal(
+        const_INT_PREFIX + const_CORE_SUFFIX, signature="sdssa{ss}a{ss}sa{ss}s"
+    )
+    def messageNew(
+        self,
+        uid,
+        timestamp,
+        from_jid,
+        to_jid,
+        message,
+        subject,
+        mess_type,
+        extra,
+        profile,
+    ):
         pass
 
-    @dbus.service.signal(const_INT_PREFIX+const_CORE_SUFFIX,
-                         signature='sa{ss}ass')
+    @dbus.service.signal(const_INT_PREFIX + const_CORE_SUFFIX, signature="sa{ss}ass")
     def newContact(self, contact_jid, attributes, groups, profile):
         pass
 
-    @dbus.service.signal(const_INT_PREFIX+const_CORE_SUFFIX,
-                         signature='ssss')
+    @dbus.service.signal(const_INT_PREFIX + const_CORE_SUFFIX, signature="ssss")
     def paramUpdate(self, name, value, category, profile):
         pass
 
-    @dbus.service.signal(const_INT_PREFIX+const_CORE_SUFFIX,
-                         signature='ssia{ss}s')
+    @dbus.service.signal(const_INT_PREFIX + const_CORE_SUFFIX, signature="ssia{ss}s")
     def presenceUpdate(self, entity_jid, show, priority, statuses, profile):
         pass
 
-    @dbus.service.signal(const_INT_PREFIX+const_CORE_SUFFIX,
-                         signature='sss')
+    @dbus.service.signal(const_INT_PREFIX + const_CORE_SUFFIX, signature="sss")
     def progressError(self, id, error, profile):
         pass
 
-    @dbus.service.signal(const_INT_PREFIX+const_CORE_SUFFIX,
-                         signature='sa{ss}s')
+    @dbus.service.signal(const_INT_PREFIX + const_CORE_SUFFIX, signature="sa{ss}s")
     def progressFinished(self, id, metadata, profile):
         pass
 
-    @dbus.service.signal(const_INT_PREFIX+const_CORE_SUFFIX,
-                         signature='sa{ss}s')
+    @dbus.service.signal(const_INT_PREFIX + const_CORE_SUFFIX, signature="sa{ss}s")
     def progressStarted(self, id, metadata, profile):
         pass
 
-    @dbus.service.signal(const_INT_PREFIX+const_CORE_SUFFIX,
-                         signature='sss')
+    @dbus.service.signal(const_INT_PREFIX + const_CORE_SUFFIX, signature="sss")
     def subscribe(self, sub_type, entity_jid, profile):
         pass
 
     ### methods ###
 
-    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
-                         in_signature='s', out_signature='a(a{ss}si)',
-                         async_callbacks=None)
+    @dbus.service.method(
+        const_INT_PREFIX + const_CORE_SUFFIX,
+        in_signature="s",
+        out_signature="a(a{ss}si)",
+        async_callbacks=None,
+    )
     def actionsGet(self, profile_key="@DEFAULT@"):
         return self._callback("actionsGet", unicode(profile_key))
 
-    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
-                         in_signature='ss', out_signature='',
-                         async_callbacks=None)
+    @dbus.service.method(
+        const_INT_PREFIX + const_CORE_SUFFIX,
+        in_signature="ss",
+        out_signature="",
+        async_callbacks=None,
+    )
     def addContact(self, entity_jid, profile_key="@DEFAULT@"):
         return self._callback("addContact", unicode(entity_jid), unicode(profile_key))
 
-    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
-                         in_signature='s', out_signature='',
-                         async_callbacks=('callback', 'errback'))
+    @dbus.service.method(
+        const_INT_PREFIX + const_CORE_SUFFIX,
+        in_signature="s",
+        out_signature="",
+        async_callbacks=("callback", "errback"),
+    )
     def asyncDeleteProfile(self, profile, callback=None, errback=None):
-        return self._callback("asyncDeleteProfile", unicode(profile), callback=callback, errback=errback)
+        return self._callback(
+            "asyncDeleteProfile", unicode(profile), callback=callback, errback=errback
+        )
 
-    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
-                         in_signature='sssis', out_signature='s',
-                         async_callbacks=('callback', 'errback'))
-    def asyncGetParamA(self, name, category, attribute="value", security_limit=-1, profile_key="@DEFAULT@", callback=None, errback=None):
-        return self._callback("asyncGetParamA", unicode(name), unicode(category), unicode(attribute), security_limit, unicode(profile_key), callback=callback, errback=errback)
+    @dbus.service.method(
+        const_INT_PREFIX + const_CORE_SUFFIX,
+        in_signature="sssis",
+        out_signature="s",
+        async_callbacks=("callback", "errback"),
+    )
+    def asyncGetParamA(
+        self,
+        name,
+        category,
+        attribute="value",
+        security_limit=-1,
+        profile_key="@DEFAULT@",
+        callback=None,
+        errback=None,
+    ):
+        return self._callback(
+            "asyncGetParamA",
+            unicode(name),
+            unicode(category),
+            unicode(attribute),
+            security_limit,
+            unicode(profile_key),
+            callback=callback,
+            errback=errback,
+        )
 
-    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
-                         in_signature='sis', out_signature='a{ss}',
-                         async_callbacks=('callback', 'errback'))
-    def asyncGetParamsValuesFromCategory(self, category, security_limit=-1, profile_key="@DEFAULT@", callback=None, errback=None):
-        return self._callback("asyncGetParamsValuesFromCategory", unicode(category), security_limit, unicode(profile_key), callback=callback, errback=errback)
+    @dbus.service.method(
+        const_INT_PREFIX + const_CORE_SUFFIX,
+        in_signature="sis",
+        out_signature="a{ss}",
+        async_callbacks=("callback", "errback"),
+    )
+    def asyncGetParamsValuesFromCategory(
+        self,
+        category,
+        security_limit=-1,
+        profile_key="@DEFAULT@",
+        callback=None,
+        errback=None,
+    ):
+        return self._callback(
+            "asyncGetParamsValuesFromCategory",
+            unicode(category),
+            security_limit,
+            unicode(profile_key),
+            callback=callback,
+            errback=errback,
+        )
 
-    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
-                         in_signature='ssa{ss}', out_signature='b',
-                         async_callbacks=('callback', 'errback'))
-    def connect(self, profile_key="@DEFAULT@", password='', options={}, callback=None, errback=None):
-        return self._callback("connect", unicode(profile_key), unicode(password), options, callback=callback, errback=errback)
+    @dbus.service.method(
+        const_INT_PREFIX + const_CORE_SUFFIX,
+        in_signature="ssa{ss}",
+        out_signature="b",
+        async_callbacks=("callback", "errback"),
+    )
+    def connect(
+        self,
+        profile_key="@DEFAULT@",
+        password="",
+        options={},
+        callback=None,
+        errback=None,
+    ):
+        return self._callback(
+            "connect",
+            unicode(profile_key),
+            unicode(password),
+            options,
+            callback=callback,
+            errback=errback,
+        )
+
+    @dbus.service.method(
+        const_INT_PREFIX + const_CORE_SUFFIX,
+        in_signature="ss",
+        out_signature="",
+        async_callbacks=("callback", "errback"),
+    )
+    def delContact(
+        self, entity_jid, profile_key="@DEFAULT@", callback=None, errback=None
+    ):
+        return self._callback(
+            "delContact",
+            unicode(entity_jid),
+            unicode(profile_key),
+            callback=callback,
+            errback=errback,
+        )
 
-    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
-                         in_signature='ss', out_signature='',
-                         async_callbacks=('callback', 'errback'))
-    def delContact(self, entity_jid, profile_key="@DEFAULT@", callback=None, errback=None):
-        return self._callback("delContact", unicode(entity_jid), unicode(profile_key), callback=callback, errback=errback)
-
-    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
-                         in_signature='asa(ss)bbbbbs', out_signature='(a{sa(sss)}a{sa(sss)}a{sa(sss)})',
-                         async_callbacks=('callback', 'errback'))
-    def discoFindByFeatures(self, namespaces, identities, bare_jid=False, service=True, roster=True, own_jid=True, local_device=False, profile_key=u"@DEFAULT@", callback=None, errback=None):
-        return self._callback("discoFindByFeatures", namespaces, identities, bare_jid, service, roster, own_jid, local_device, unicode(profile_key), callback=callback, errback=errback)
+    @dbus.service.method(
+        const_INT_PREFIX + const_CORE_SUFFIX,
+        in_signature="asa(ss)bbbbbs",
+        out_signature="(a{sa(sss)}a{sa(sss)}a{sa(sss)})",
+        async_callbacks=("callback", "errback"),
+    )
+    def discoFindByFeatures(
+        self,
+        namespaces,
+        identities,
+        bare_jid=False,
+        service=True,
+        roster=True,
+        own_jid=True,
+        local_device=False,
+        profile_key=u"@DEFAULT@",
+        callback=None,
+        errback=None,
+    ):
+        return self._callback(
+            "discoFindByFeatures",
+            namespaces,
+            identities,
+            bare_jid,
+            service,
+            roster,
+            own_jid,
+            local_device,
+            unicode(profile_key),
+            callback=callback,
+            errback=errback,
+        )
 
-    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
-                         in_signature='ssbs', out_signature='(asa(sss)a{sa(a{ss}as)})',
-                         async_callbacks=('callback', 'errback'))
-    def discoInfos(self, entity_jid, node=u'', use_cache=True, profile_key=u"@DEFAULT@", callback=None, errback=None):
-        return self._callback("discoInfos", unicode(entity_jid), unicode(node), use_cache, unicode(profile_key), callback=callback, errback=errback)
+    @dbus.service.method(
+        const_INT_PREFIX + const_CORE_SUFFIX,
+        in_signature="ssbs",
+        out_signature="(asa(sss)a{sa(a{ss}as)})",
+        async_callbacks=("callback", "errback"),
+    )
+    def discoInfos(
+        self,
+        entity_jid,
+        node=u"",
+        use_cache=True,
+        profile_key=u"@DEFAULT@",
+        callback=None,
+        errback=None,
+    ):
+        return self._callback(
+            "discoInfos",
+            unicode(entity_jid),
+            unicode(node),
+            use_cache,
+            unicode(profile_key),
+            callback=callback,
+            errback=errback,
+        )
 
-    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
-                         in_signature='ssbs', out_signature='a(sss)',
-                         async_callbacks=('callback', 'errback'))
-    def discoItems(self, entity_jid, node=u'', use_cache=True, profile_key=u"@DEFAULT@", callback=None, errback=None):
-        return self._callback("discoItems", unicode(entity_jid), unicode(node), use_cache, unicode(profile_key), callback=callback, errback=errback)
+    @dbus.service.method(
+        const_INT_PREFIX + const_CORE_SUFFIX,
+        in_signature="ssbs",
+        out_signature="a(sss)",
+        async_callbacks=("callback", "errback"),
+    )
+    def discoItems(
+        self,
+        entity_jid,
+        node=u"",
+        use_cache=True,
+        profile_key=u"@DEFAULT@",
+        callback=None,
+        errback=None,
+    ):
+        return self._callback(
+            "discoItems",
+            unicode(entity_jid),
+            unicode(node),
+            use_cache,
+            unicode(profile_key),
+            callback=callback,
+            errback=errback,
+        )
 
-    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
-                         in_signature='s', out_signature='',
-                         async_callbacks=('callback', 'errback'))
+    @dbus.service.method(
+        const_INT_PREFIX + const_CORE_SUFFIX,
+        in_signature="s",
+        out_signature="",
+        async_callbacks=("callback", "errback"),
+    )
     def disconnect(self, profile_key="@DEFAULT@", callback=None, errback=None):
-        return self._callback("disconnect", unicode(profile_key), callback=callback, errback=errback)
+        return self._callback(
+            "disconnect", unicode(profile_key), callback=callback, errback=errback
+        )
 
-    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
-                         in_signature='ss', out_signature='s',
-                         async_callbacks=None)
+    @dbus.service.method(
+        const_INT_PREFIX + const_CORE_SUFFIX,
+        in_signature="ss",
+        out_signature="s",
+        async_callbacks=None,
+    )
     def getConfig(self, section, name):
         return self._callback("getConfig", unicode(section), unicode(name))
 
-    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
-                         in_signature='s', out_signature='a(sa{ss}as)',
-                         async_callbacks=('callback', 'errback'))
+    @dbus.service.method(
+        const_INT_PREFIX + const_CORE_SUFFIX,
+        in_signature="s",
+        out_signature="a(sa{ss}as)",
+        async_callbacks=("callback", "errback"),
+    )
     def getContacts(self, profile_key="@DEFAULT@", callback=None, errback=None):
-        return self._callback("getContacts", unicode(profile_key), callback=callback, errback=errback)
+        return self._callback(
+            "getContacts", unicode(profile_key), callback=callback, errback=errback
+        )
 
-    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
-                         in_signature='ss', out_signature='as',
-                         async_callbacks=None)
+    @dbus.service.method(
+        const_INT_PREFIX + const_CORE_SUFFIX,
+        in_signature="ss",
+        out_signature="as",
+        async_callbacks=None,
+    )
     def getContactsFromGroup(self, group, profile_key="@DEFAULT@"):
-        return self._callback("getContactsFromGroup", unicode(group), unicode(profile_key))
+        return self._callback(
+            "getContactsFromGroup", unicode(group), unicode(profile_key)
+        )
 
-    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
-                         in_signature='asass', out_signature='a{sa{ss}}',
-                         async_callbacks=None)
+    @dbus.service.method(
+        const_INT_PREFIX + const_CORE_SUFFIX,
+        in_signature="asass",
+        out_signature="a{sa{ss}}",
+        async_callbacks=None,
+    )
     def getEntitiesData(self, jids, keys, profile):
         return self._callback("getEntitiesData", jids, keys, unicode(profile))
 
-    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
-                         in_signature='sass', out_signature='a{ss}',
-                         async_callbacks=None)
+    @dbus.service.method(
+        const_INT_PREFIX + const_CORE_SUFFIX,
+        in_signature="sass",
+        out_signature="a{ss}",
+        async_callbacks=None,
+    )
     def getEntityData(self, jid, keys, profile):
         return self._callback("getEntityData", unicode(jid), keys, unicode(profile))
 
-    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
-                         in_signature='s', out_signature='a{sa{ss}}',
-                         async_callbacks=('callback', 'errback'))
+    @dbus.service.method(
+        const_INT_PREFIX + const_CORE_SUFFIX,
+        in_signature="s",
+        out_signature="a{sa{ss}}",
+        async_callbacks=("callback", "errback"),
+    )
     def getFeatures(self, profile_key, callback=None, errback=None):
-        return self._callback("getFeatures", unicode(profile_key), callback=callback, errback=errback)
+        return self._callback(
+            "getFeatures", unicode(profile_key), callback=callback, errback=errback
+        )
 
-    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
-                         in_signature='ss', out_signature='s',
-                         async_callbacks=None)
+    @dbus.service.method(
+        const_INT_PREFIX + const_CORE_SUFFIX,
+        in_signature="ss",
+        out_signature="s",
+        async_callbacks=None,
+    )
     def getMainResource(self, contact_jid, profile_key="@DEFAULT@"):
-        return self._callback("getMainResource", unicode(contact_jid), unicode(profile_key))
+        return self._callback(
+            "getMainResource", unicode(contact_jid), unicode(profile_key)
+        )
 
-    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
-                         in_signature='ssss', out_signature='s',
-                         async_callbacks=None)
+    @dbus.service.method(
+        const_INT_PREFIX + const_CORE_SUFFIX,
+        in_signature="ssss",
+        out_signature="s",
+        async_callbacks=None,
+    )
     def getParamA(self, name, category, attribute="value", profile_key="@DEFAULT@"):
-        return self._callback("getParamA", unicode(name), unicode(category), unicode(attribute), unicode(profile_key))
+        return self._callback(
+            "getParamA",
+            unicode(name),
+            unicode(category),
+            unicode(attribute),
+            unicode(profile_key),
+        )
 
-    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
-                         in_signature='', out_signature='as',
-                         async_callbacks=None)
-    def getParamsCategories(self, ):
-        return self._callback("getParamsCategories", )
+    @dbus.service.method(
+        const_INT_PREFIX + const_CORE_SUFFIX,
+        in_signature="",
+        out_signature="as",
+        async_callbacks=None,
+    )
+    def getParamsCategories(self,):
+        return self._callback("getParamsCategories")
 
-    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
-                         in_signature='iss', out_signature='s',
-                         async_callbacks=('callback', 'errback'))
-    def getParamsUI(self, security_limit=-1, app='', profile_key="@DEFAULT@", callback=None, errback=None):
-        return self._callback("getParamsUI", security_limit, unicode(app), unicode(profile_key), callback=callback, errback=errback)
+    @dbus.service.method(
+        const_INT_PREFIX + const_CORE_SUFFIX,
+        in_signature="iss",
+        out_signature="s",
+        async_callbacks=("callback", "errback"),
+    )
+    def getParamsUI(
+        self,
+        security_limit=-1,
+        app="",
+        profile_key="@DEFAULT@",
+        callback=None,
+        errback=None,
+    ):
+        return self._callback(
+            "getParamsUI",
+            security_limit,
+            unicode(app),
+            unicode(profile_key),
+            callback=callback,
+            errback=errback,
+        )
 
-    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
-                         in_signature='s', out_signature='a{sa{s(sia{ss})}}',
-                         async_callbacks=None)
+    @dbus.service.method(
+        const_INT_PREFIX + const_CORE_SUFFIX,
+        in_signature="s",
+        out_signature="a{sa{s(sia{ss})}}",
+        async_callbacks=None,
+    )
     def getPresenceStatuses(self, profile_key="@DEFAULT@"):
         return self._callback("getPresenceStatuses", unicode(profile_key))
 
-    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
-                         in_signature='', out_signature='',
-                         async_callbacks=('callback', 'errback'))
+    @dbus.service.method(
+        const_INT_PREFIX + const_CORE_SUFFIX,
+        in_signature="",
+        out_signature="",
+        async_callbacks=("callback", "errback"),
+    )
     def getReady(self, callback=None, errback=None):
         return self._callback("getReady", callback=callback, errback=errback)
 
-    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
-                         in_signature='', out_signature='s',
-                         async_callbacks=None)
-    def getVersion(self, ):
-        return self._callback("getVersion", )
+    @dbus.service.method(
+        const_INT_PREFIX + const_CORE_SUFFIX,
+        in_signature="",
+        out_signature="s",
+        async_callbacks=None,
+    )
+    def getVersion(self,):
+        return self._callback("getVersion")
 
-    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
-                         in_signature='s', out_signature='a{ss}',
-                         async_callbacks=None)
+    @dbus.service.method(
+        const_INT_PREFIX + const_CORE_SUFFIX,
+        in_signature="s",
+        out_signature="a{ss}",
+        async_callbacks=None,
+    )
     def getWaitingSub(self, profile_key="@DEFAULT@"):
         return self._callback("getWaitingSub", unicode(profile_key))
 
-    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
-                         in_signature='ssiba{ss}s', out_signature='a(sdssa{ss}a{ss}sa{ss})',
-                         async_callbacks=('callback', 'errback'))
-    def historyGet(self, from_jid, to_jid, limit, between=True, filters='', profile="@NONE@", callback=None, errback=None):
-        return self._callback("historyGet", unicode(from_jid), unicode(to_jid), limit, between, filters, unicode(profile), callback=callback, errback=errback)
+    @dbus.service.method(
+        const_INT_PREFIX + const_CORE_SUFFIX,
+        in_signature="ssiba{ss}s",
+        out_signature="a(sdssa{ss}a{ss}sa{ss})",
+        async_callbacks=("callback", "errback"),
+    )
+    def historyGet(
+        self,
+        from_jid,
+        to_jid,
+        limit,
+        between=True,
+        filters="",
+        profile="@NONE@",
+        callback=None,
+        errback=None,
+    ):
+        return self._callback(
+            "historyGet",
+            unicode(from_jid),
+            unicode(to_jid),
+            limit,
+            between,
+            filters,
+            unicode(profile),
+            callback=callback,
+            errback=errback,
+        )
 
-    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
-                         in_signature='s', out_signature='b',
-                         async_callbacks=None)
+    @dbus.service.method(
+        const_INT_PREFIX + const_CORE_SUFFIX,
+        in_signature="s",
+        out_signature="b",
+        async_callbacks=None,
+    )
     def isConnected(self, profile_key="@DEFAULT@"):
         return self._callback("isConnected", unicode(profile_key))
 
-    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
-                         in_signature='sa{ss}s', out_signature='a{ss}',
-                         async_callbacks=('callback', 'errback'))
-    def launchAction(self, callback_id, data, profile_key="@DEFAULT@", callback=None, errback=None):
-        return self._callback("launchAction", unicode(callback_id), data, unicode(profile_key), callback=callback, errback=errback)
+    @dbus.service.method(
+        const_INT_PREFIX + const_CORE_SUFFIX,
+        in_signature="sa{ss}s",
+        out_signature="a{ss}",
+        async_callbacks=("callback", "errback"),
+    )
+    def launchAction(
+        self, callback_id, data, profile_key="@DEFAULT@", callback=None, errback=None
+    ):
+        return self._callback(
+            "launchAction",
+            unicode(callback_id),
+            data,
+            unicode(profile_key),
+            callback=callback,
+            errback=errback,
+        )
 
-    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
-                         in_signature='s', out_signature='b',
-                         async_callbacks=None)
+    @dbus.service.method(
+        const_INT_PREFIX + const_CORE_SUFFIX,
+        in_signature="s",
+        out_signature="b",
+        async_callbacks=None,
+    )
     def loadParamsTemplate(self, filename):
         return self._callback("loadParamsTemplate", unicode(filename))
 
-    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
-                         in_signature='ss', out_signature='s',
-                         async_callbacks=None)
+    @dbus.service.method(
+        const_INT_PREFIX + const_CORE_SUFFIX,
+        in_signature="ss",
+        out_signature="s",
+        async_callbacks=None,
+    )
     def menuHelpGet(self, menu_id, language):
         return self._callback("menuHelpGet", unicode(menu_id), unicode(language))
 
-    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
-                         in_signature='sasa{ss}is', out_signature='a{ss}',
-                         async_callbacks=('callback', 'errback'))
-    def menuLaunch(self, menu_type, path, data, security_limit, profile_key, callback=None, errback=None):
-        return self._callback("menuLaunch", unicode(menu_type), path, data, security_limit, unicode(profile_key), callback=callback, errback=errback)
+    @dbus.service.method(
+        const_INT_PREFIX + const_CORE_SUFFIX,
+        in_signature="sasa{ss}is",
+        out_signature="a{ss}",
+        async_callbacks=("callback", "errback"),
+    )
+    def menuLaunch(
+        self,
+        menu_type,
+        path,
+        data,
+        security_limit,
+        profile_key,
+        callback=None,
+        errback=None,
+    ):
+        return self._callback(
+            "menuLaunch",
+            unicode(menu_type),
+            path,
+            data,
+            security_limit,
+            unicode(profile_key),
+            callback=callback,
+            errback=errback,
+        )
 
-    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
-                         in_signature='si', out_signature='a(ssasasa{ss})',
-                         async_callbacks=None)
+    @dbus.service.method(
+        const_INT_PREFIX + const_CORE_SUFFIX,
+        in_signature="si",
+        out_signature="a(ssasasa{ss})",
+        async_callbacks=None,
+    )
     def menusGet(self, language, security_limit):
         return self._callback("menusGet", unicode(language), security_limit)
 
-    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
-                         in_signature='sa{ss}a{ss}sa{ss}s', out_signature='',
-                         async_callbacks=('callback', 'errback'))
-    def messageSend(self, to_jid, message, subject={}, mess_type="auto", extra={}, profile_key="@NONE@", callback=None, errback=None):
-        return self._callback("messageSend", unicode(to_jid), message, subject, unicode(mess_type), extra, unicode(profile_key), callback=callback, errback=errback)
-
-    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
-                         in_signature='', out_signature='a{ss}',
-                         async_callbacks=None)
-    def namespacesGet(self, ):
-        return self._callback("namespacesGet", )
+    @dbus.service.method(
+        const_INT_PREFIX + const_CORE_SUFFIX,
+        in_signature="sa{ss}a{ss}sa{ss}s",
+        out_signature="",
+        async_callbacks=("callback", "errback"),
+    )
+    def messageSend(
+        self,
+        to_jid,
+        message,
+        subject={},
+        mess_type="auto",
+        extra={},
+        profile_key="@NONE@",
+        callback=None,
+        errback=None,
+    ):
+        return self._callback(
+            "messageSend",
+            unicode(to_jid),
+            message,
+            subject,
+            unicode(mess_type),
+            extra,
+            unicode(profile_key),
+            callback=callback,
+            errback=errback,
+        )
 
-    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
-                         in_signature='sis', out_signature='',
-                         async_callbacks=None)
-    def paramsRegisterApp(self, xml, security_limit=-1, app=''):
-        return self._callback("paramsRegisterApp", unicode(xml), security_limit, unicode(app))
+    @dbus.service.method(
+        const_INT_PREFIX + const_CORE_SUFFIX,
+        in_signature="",
+        out_signature="a{ss}",
+        async_callbacks=None,
+    )
+    def namespacesGet(self,):
+        return self._callback("namespacesGet")
+
+    @dbus.service.method(
+        const_INT_PREFIX + const_CORE_SUFFIX,
+        in_signature="sis",
+        out_signature="",
+        async_callbacks=None,
+    )
+    def paramsRegisterApp(self, xml, security_limit=-1, app=""):
+        return self._callback(
+            "paramsRegisterApp", unicode(xml), security_limit, unicode(app)
+        )
 
-    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
-                         in_signature='sss', out_signature='',
-                         async_callbacks=('callback', 'errback'))
-    def profileCreate(self, profile, password='', component='', callback=None, errback=None):
-        return self._callback("profileCreate", unicode(profile), unicode(password), unicode(component), callback=callback, errback=errback)
+    @dbus.service.method(
+        const_INT_PREFIX + const_CORE_SUFFIX,
+        in_signature="sss",
+        out_signature="",
+        async_callbacks=("callback", "errback"),
+    )
+    def profileCreate(
+        self, profile, password="", component="", callback=None, errback=None
+    ):
+        return self._callback(
+            "profileCreate",
+            unicode(profile),
+            unicode(password),
+            unicode(component),
+            callback=callback,
+            errback=errback,
+        )
 
-    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
-                         in_signature='s', out_signature='b',
-                         async_callbacks=None)
+    @dbus.service.method(
+        const_INT_PREFIX + const_CORE_SUFFIX,
+        in_signature="s",
+        out_signature="b",
+        async_callbacks=None,
+    )
     def profileIsSessionStarted(self, profile_key="@DEFAULT@"):
         return self._callback("profileIsSessionStarted", unicode(profile_key))
 
-    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
-                         in_signature='s', out_signature='s',
-                         async_callbacks=None)
+    @dbus.service.method(
+        const_INT_PREFIX + const_CORE_SUFFIX,
+        in_signature="s",
+        out_signature="s",
+        async_callbacks=None,
+    )
     def profileNameGet(self, profile_key="@DEFAULT@"):
         return self._callback("profileNameGet", unicode(profile_key))
 
-    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
-                         in_signature='s', out_signature='',
-                         async_callbacks=None)
+    @dbus.service.method(
+        const_INT_PREFIX + const_CORE_SUFFIX,
+        in_signature="s",
+        out_signature="",
+        async_callbacks=None,
+    )
     def profileSetDefault(self, profile):
         return self._callback("profileSetDefault", unicode(profile))
 
-    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
-                         in_signature='ss', out_signature='b',
-                         async_callbacks=('callback', 'errback'))
-    def profileStartSession(self, password='', profile_key="@DEFAULT@", callback=None, errback=None):
-        return self._callback("profileStartSession", unicode(password), unicode(profile_key), callback=callback, errback=errback)
+    @dbus.service.method(
+        const_INT_PREFIX + const_CORE_SUFFIX,
+        in_signature="ss",
+        out_signature="b",
+        async_callbacks=("callback", "errback"),
+    )
+    def profileStartSession(
+        self, password="", profile_key="@DEFAULT@", callback=None, errback=None
+    ):
+        return self._callback(
+            "profileStartSession",
+            unicode(password),
+            unicode(profile_key),
+            callback=callback,
+            errback=errback,
+        )
 
-    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
-                         in_signature='bb', out_signature='as',
-                         async_callbacks=None)
+    @dbus.service.method(
+        const_INT_PREFIX + const_CORE_SUFFIX,
+        in_signature="bb",
+        out_signature="as",
+        async_callbacks=None,
+    )
     def profilesListGet(self, clients=True, components=False):
         return self._callback("profilesListGet", clients, components)
 
-    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
-                         in_signature='ss', out_signature='a{ss}',
-                         async_callbacks=None)
+    @dbus.service.method(
+        const_INT_PREFIX + const_CORE_SUFFIX,
+        in_signature="ss",
+        out_signature="a{ss}",
+        async_callbacks=None,
+    )
     def progressGet(self, id, profile):
         return self._callback("progressGet", unicode(id), unicode(profile))
 
-    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
-                         in_signature='s', out_signature='a{sa{sa{ss}}}',
-                         async_callbacks=None)
+    @dbus.service.method(
+        const_INT_PREFIX + const_CORE_SUFFIX,
+        in_signature="s",
+        out_signature="a{sa{sa{ss}}}",
+        async_callbacks=None,
+    )
     def progressGetAll(self, profile):
         return self._callback("progressGetAll", unicode(profile))
 
-    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
-                         in_signature='s', out_signature='a{sa{sa{ss}}}',
-                         async_callbacks=None)
+    @dbus.service.method(
+        const_INT_PREFIX + const_CORE_SUFFIX,
+        in_signature="s",
+        out_signature="a{sa{sa{ss}}}",
+        async_callbacks=None,
+    )
     def progressGetAllMetadata(self, profile):
         return self._callback("progressGetAllMetadata", unicode(profile))
 
-    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
-                         in_signature='s', out_signature='b',
-                         async_callbacks=None)
+    @dbus.service.method(
+        const_INT_PREFIX + const_CORE_SUFFIX,
+        in_signature="s",
+        out_signature="b",
+        async_callbacks=None,
+    )
     def saveParamsTemplate(self, filename):
         return self._callback("saveParamsTemplate", unicode(filename))
 
-    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
-                         in_signature='s', out_signature='a{ss}',
-                         async_callbacks=('callback', 'errback'))
+    @dbus.service.method(
+        const_INT_PREFIX + const_CORE_SUFFIX,
+        in_signature="s",
+        out_signature="a{ss}",
+        async_callbacks=("callback", "errback"),
+    )
     def sessionInfosGet(self, profile_key, callback=None, errback=None):
-        return self._callback("sessionInfosGet", unicode(profile_key), callback=callback, errback=errback)
+        return self._callback(
+            "sessionInfosGet", unicode(profile_key), callback=callback, errback=errback
+        )
 
-    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
-                         in_signature='sssis', out_signature='',
-                         async_callbacks=None)
+    @dbus.service.method(
+        const_INT_PREFIX + const_CORE_SUFFIX,
+        in_signature="sssis",
+        out_signature="",
+        async_callbacks=None,
+    )
     def setParam(self, name, value, category, security_limit=-1, profile_key="@DEFAULT@"):
-        return self._callback("setParam", unicode(name), unicode(value), unicode(category), security_limit, unicode(profile_key))
+        return self._callback(
+            "setParam",
+            unicode(name),
+            unicode(value),
+            unicode(category),
+            security_limit,
+            unicode(profile_key),
+        )
 
-    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
-                         in_signature='ssa{ss}s', out_signature='',
-                         async_callbacks=None)
-    def setPresence(self, to_jid='', show='', statuses={}, profile_key="@DEFAULT@"):
-        return self._callback("setPresence", unicode(to_jid), unicode(show), statuses, unicode(profile_key))
+    @dbus.service.method(
+        const_INT_PREFIX + const_CORE_SUFFIX,
+        in_signature="ssa{ss}s",
+        out_signature="",
+        async_callbacks=None,
+    )
+    def setPresence(self, to_jid="", show="", statuses={}, profile_key="@DEFAULT@"):
+        return self._callback(
+            "setPresence", unicode(to_jid), unicode(show), statuses, unicode(profile_key)
+        )
+
+    @dbus.service.method(
+        const_INT_PREFIX + const_CORE_SUFFIX,
+        in_signature="sss",
+        out_signature="",
+        async_callbacks=None,
+    )
+    def subscription(self, sub_type, entity, profile_key="@DEFAULT@"):
+        return self._callback(
+            "subscription", unicode(sub_type), unicode(entity), unicode(profile_key)
+        )
 
-    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
-                         in_signature='sss', out_signature='',
-                         async_callbacks=None)
-    def subscription(self, sub_type, entity, profile_key="@DEFAULT@"):
-        return self._callback("subscription", unicode(sub_type), unicode(entity), unicode(profile_key))
-
-    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
-                         in_signature='ssass', out_signature='',
-                         async_callbacks=('callback', 'errback'))
-    def updateContact(self, entity_jid, name, groups, profile_key="@DEFAULT@", callback=None, errback=None):
-        return self._callback("updateContact", unicode(entity_jid), unicode(name), groups, unicode(profile_key), callback=callback, errback=errback)
+    @dbus.service.method(
+        const_INT_PREFIX + const_CORE_SUFFIX,
+        in_signature="ssass",
+        out_signature="",
+        async_callbacks=("callback", "errback"),
+    )
+    def updateContact(
+        self,
+        entity_jid,
+        name,
+        groups,
+        profile_key="@DEFAULT@",
+        callback=None,
+        errback=None,
+    ):
+        return self._callback(
+            "updateContact",
+            unicode(entity_jid),
+            unicode(name),
+            groups,
+            unicode(profile_key),
+            callback=callback,
+            errback=errback,
+        )
 
     def __attributes(self, in_sign):
         """Return arguments to user given a in_sign
@@ -499,22 +910,24 @@
         idx = 0
         attr = []
         while i < len(in_sign):
-            if in_sign[i] not in ['b', 'y', 'n', 'i', 'x', 'q', 'u', 't', 'd', 's', 'a']:
+            if in_sign[i] not in ["b", "y", "n", "i", "x", "q", "u", "t", "d", "s", "a"]:
                 raise ParseError("Unmanaged attribute type [%c]" % in_sign[i])
 
             attr.append("arg_%i" % idx)
             idx += 1
 
-            if in_sign[i] == 'a':
+            if in_sign[i] == "a":
                 i += 1
-                if in_sign[i] != '{' and in_sign[i] != '(':  # FIXME: must manage tuples out of arrays
+                if (
+                    in_sign[i] != "{" and in_sign[i] != "("
+                ):  # FIXME: must manage tuples out of arrays
                     i += 1
                     continue  # we have a simple type for the array
                 opening_car = in_sign[i]
-                assert(opening_car in ['{', '('])
-                closing_car = '}' if opening_car == '{' else ')'
+                assert opening_car in ["{", "("]
+                closing_car = "}" if opening_car == "{" else ")"
                 opening_count = 1
-                while (True):  # we have a dict or a list of tuples
+                while True:  # we have a dict or a list of tuples
                     i += 1
                     if i >= len(in_sign):
                         raise ParseError("missing }")
@@ -535,47 +948,80 @@
         _defaults = list(inspect_args.defaults or [])
 
         if inspect.ismethod(method):
-            #if we have a method, we don't want the first argument (usually 'self')
-            del(_arguments[0])
+            # if we have a method, we don't want the first argument (usually 'self')
+            del (_arguments[0])
 
-        #first arguments are for the _callback method
-        arguments_callback = ', '.join([repr(name)] + ((_arguments + ['callback=callback', 'errback=errback']) if async else _arguments))
+        # first arguments are for the _callback method
+        arguments_callback = ", ".join(
+            [repr(name)]
+            + (
+                (_arguments + ["callback=callback", "errback=errback"])
+                if async
+                else _arguments
+            )
+        )
 
         if async:
-            _arguments.extend(['callback', 'errback'])
+            _arguments.extend(["callback", "errback"])
             _defaults.extend([None, None])
 
-        #now we create a second list with default values
+        # now we create a second list with default values
         for i in range(1, len(_defaults) + 1):
             _arguments[-i] = "%s = %s" % (_arguments[-i], repr(_defaults[-i]))
 
-        arguments_defaults = ', '.join(_arguments)
+        arguments_defaults = ", ".join(_arguments)
 
-        code = compile('def %(name)s (self,%(arguments_defaults)s): return self._callback(%(arguments_callback)s)' %
-                       {'name': name, 'arguments_defaults': arguments_defaults, 'arguments_callback': arguments_callback}, '<DBus bridge>', 'exec')
-        exec (code)  # FIXME: to the same thing in a cleaner way, without compile/exec
+        code = compile(
+            "def %(name)s (self,%(arguments_defaults)s): return self._callback(%(arguments_callback)s)"
+            % {
+                "name": name,
+                "arguments_defaults": arguments_defaults,
+                "arguments_callback": arguments_callback,
+            },
+            "<DBus bridge>",
+            "exec",
+        )
+        exec(code)  # FIXME: to the same thing in a cleaner way, without compile/exec
         method = locals()[name]
-        async_callbacks = ('callback', 'errback') if async else None
-        setattr(DbusObject, name, dbus.service.method(
-            const_INT_PREFIX + int_suffix, in_signature=in_sign, out_signature=out_sign,
-            async_callbacks=async_callbacks)(method))
+        async_callbacks = ("callback", "errback") if async else None
+        setattr(
+            DbusObject,
+            name,
+            dbus.service.method(
+                const_INT_PREFIX + int_suffix,
+                in_signature=in_sign,
+                out_signature=out_sign,
+                async_callbacks=async_callbacks,
+            )(method),
+        )
         function = getattr(self, name)
-        func_table = self._dbus_class_table[self.__class__.__module__ + '.' + self.__class__.__name__][function._dbus_interface]
+        func_table = self._dbus_class_table[
+            self.__class__.__module__ + "." + self.__class__.__name__
+        ][function._dbus_interface]
         func_table[function.__name__] = function  # Needed for introspection
 
     def addSignal(self, name, int_suffix, signature, doc={}):
         """Dynamically add a signal to Dbus Bridge"""
-        attributes = ', '.join(self.__attributes(signature))
-        #TODO: use doc parameter to name attributes
+        attributes = ", ".join(self.__attributes(signature))
+        # TODO: use doc parameter to name attributes
 
-        #code = compile ('def '+name+' (self,'+attributes+'): log.debug ("'+name+' signal")', '<DBus bridge>','exec') #XXX: the log.debug is too annoying with xmllog
-        code = compile('def ' + name + ' (self,' + attributes + '): pass', '<DBus bridge>', 'exec')
-        exec (code)
+        # code = compile ('def '+name+' (self,'+attributes+'): log.debug ("'+name+' signal")', '<DBus bridge>','exec') #XXX: the log.debug is too annoying with xmllog
+        code = compile(
+            "def " + name + " (self," + attributes + "): pass", "<DBus bridge>", "exec"
+        )
+        exec(code)
         signal = locals()[name]
-        setattr(DbusObject, name, dbus.service.signal(
-            const_INT_PREFIX + int_suffix, signature=signature)(signal))
+        setattr(
+            DbusObject,
+            name,
+            dbus.service.signal(const_INT_PREFIX + int_suffix, signature=signature)(
+                signal
+            ),
+        )
         function = getattr(self, name)
-        func_table = self._dbus_class_table[self.__class__.__module__ + '.' + self.__class__.__name__][function._dbus_interface]
+        func_table = self._dbus_class_table[
+            self.__class__.__module__ + "." + self.__class__.__name__
+        ][function._dbus_interface]
         func_table[function.__name__] = function  # Needed for introspection
 
 
@@ -586,8 +1032,12 @@
         try:
             self.session_bus = dbus.SessionBus()
         except dbus.DBusException as e:
-            if e._dbus_error_name == 'org.freedesktop.DBus.Error.NotSupported':
-                log.error(_(u"D-Bus is not launched, please see README to see instructions on how to launch it"))
+            if e._dbus_error_name == "org.freedesktop.DBus.Error.NotSupported":
+                log.error(
+                    _(
+                        u"D-Bus is not launched, please see README to see instructions on how to launch it"
+                    )
+                )
             raise BridgeInitError
         self.dbus_name = dbus.service.BusName(const_INT_PREFIX, self.session_bus)
         self.dbus_bridge = DbusObject(self.session_bus, const_OBJ_PATH)
@@ -607,8 +1057,21 @@
     def entityDataUpdated(self, jid, name, value, profile):
         self.dbus_bridge.entityDataUpdated(jid, name, value, profile)
 
-    def messageNew(self, uid, timestamp, from_jid, to_jid, message, subject, mess_type, extra, profile):
-        self.dbus_bridge.messageNew(uid, timestamp, from_jid, to_jid, message, subject, mess_type, extra, profile)
+    def messageNew(
+        self,
+        uid,
+        timestamp,
+        from_jid,
+        to_jid,
+        message,
+        subject,
+        mess_type,
+        extra,
+        profile,
+    ):
+        self.dbus_bridge.messageNew(
+            uid, timestamp, from_jid, to_jid, message, subject, mess_type, extra, profile
+        )
 
     def newContact(self, contact_jid, attributes, groups, profile):
         self.dbus_bridge.newContact(contact_jid, attributes, groups, profile)
@@ -637,11 +1100,11 @@
 
     def addMethod(self, name, int_suffix, in_sign, out_sign, method, async=False, doc={}):
         """Dynamically add a method to Dbus Bridge"""
-        #FIXME: doc parameter is kept only temporary, the time to remove it from calls
+        # FIXME: doc parameter is kept only temporary, the time to remove it from calls
         log.debug("Adding method [%s] to DBus bridge" % name)
         self.dbus_bridge.addMethod(name, int_suffix, in_sign, out_sign, method, async)
         self.register_method(name, method)
 
     def addSignal(self, name, int_suffix, signature, doc={}):
         self.dbus_bridge.addSignal(name, int_suffix, signature, doc)
-        setattr(Bridge, name, getattr(self.dbus_bridge, name))
\ No newline at end of file
+        setattr(Bridge, name, getattr(self.dbus_bridge, name))
--- a/sat/bridge/pb.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/bridge/pb.py	Wed Jun 27 20:14:46 2018 +0200
@@ -1,5 +1,5 @@
 #!/usr/bin/env python2
-#-*- coding: utf-8 -*-
+# -*- coding: utf-8 -*-
 
 # SAT: a jabber client
 # Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org)
@@ -19,6 +19,7 @@
 
 
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from twisted.spread import jelly, pb
 from twisted.internet import reactor
@@ -28,17 +29,18 @@
 # we monkey patch jelly to handle namedtuple
 ori_jelly = jelly._Jellier.jelly
 
+
 def fixed_jelly(self, obj):
     """this method fix handling of namedtuple"""
     if isinstance(obj, tuple) and not obj is tuple:
         obj = tuple(obj)
     return ori_jelly(self, obj)
 
+
 jelly._Jellier.jelly = fixed_jelly
 
 
 class PBRoot(pb.Root):
-
     def __init__(self):
         self.signals_handlers = []
 
@@ -47,10 +49,11 @@
         log.info(u"registered signal handler")
 
     def sendSignalEb(self, failure, signal_name):
-        log.error(u"Error while sending signal {name}: {msg}".format(
-            name = signal_name,
-            msg = failure,
-            ))
+        log.error(
+            u"Error while sending signal {name}: {msg}".format(
+                name=signal_name, msg=failure
+            )
+        )
 
     def sendSignal(self, name, args, kwargs):
         to_remove = []
@@ -66,11 +69,11 @@
                 log.debug(u"Removing signal handler for dead frontend")
                 self.signals_handlers.remove(handler)
 
+
 ##METHODS_PART##
 
 
 class Bridge(object):
-
     def __init__(self):
         log.info("Init Perspective Broker...")
         self.root = PBRoot()
@@ -85,18 +88,20 @@
 
     def register_method(self, name, callback):
         log.debug("registering PB bridge method [%s]" % name)
-        setattr(self.root, "remote_"+name, callback)
-        # self.root.register_method(name, callback)
+        setattr(self.root, "remote_" + name, callback)
+        #  self.root.register_method(name, callback)
 
     def addMethod(self, name, int_suffix, in_sign, out_sign, method, async=False, doc={}):
         """Dynamically add a method to PB Bridge"""
-        #FIXME: doc parameter is kept only temporary, the time to remove it from calls
+        # FIXME: doc parameter is kept only temporary, the time to remove it from calls
         log.debug("Adding method {name} to PB bridge".format(name=name))
         self.register_method(name, method)
 
     def addSignal(self, name, int_suffix, signature, doc={}):
         log.debug("Adding signal {name} to PB bridge".format(name=name))
-        setattr(self, name, lambda *args, **kwargs: self.sendSignal(name, *args, **kwargs))
+        setattr(
+            self, name, lambda *args, **kwargs: self.sendSignal(name, *args, **kwargs)
+        )
 
     def actionNew(self, action_data, id, security_limit, profile):
         self.sendSignal("actionNew", action_data, id, security_limit, profile)
@@ -113,8 +118,30 @@
     def entityDataUpdated(self, jid, name, value, profile):
         self.sendSignal("entityDataUpdated", jid, name, value, profile)
 
-    def messageNew(self, uid, timestamp, from_jid, to_jid, message, subject, mess_type, extra, profile):
-        self.sendSignal("messageNew", uid, timestamp, from_jid, to_jid, message, subject, mess_type, extra, profile)
+    def messageNew(
+        self,
+        uid,
+        timestamp,
+        from_jid,
+        to_jid,
+        message,
+        subject,
+        mess_type,
+        extra,
+        profile,
+    ):
+        self.sendSignal(
+            "messageNew",
+            uid,
+            timestamp,
+            from_jid,
+            to_jid,
+            message,
+            subject,
+            mess_type,
+            extra,
+            profile,
+        )
 
     def newContact(self, contact_jid, attributes, groups, profile):
         self.sendSignal("newContact", contact_jid, attributes, groups, profile)
--- a/sat/core/constants.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/core/constants.py	Wed Jun 27 20:14:46 2018 +0200
@@ -28,23 +28,24 @@
 class Const(object):
 
     ## Application ##
-    APP_NAME = u'Salut à Toi'
-    APP_NAME_SHORT = u'SàT'
-    APP_NAME_FILE = u'sat'
-    APP_NAME_FULL = u'{name_short} ({name})'.format(name_short=APP_NAME_SHORT,
-                                                    name=APP_NAME)
-    APP_VERSION = sat.__version__  # Please add 'D' at the end of version in sat/VERSION for dev versions
-    APP_RELEASE_NAME = u'La Commune'
-    APP_URL = u'https://salut-a-toi.org'
-
+    APP_NAME = u"Salut à Toi"
+    APP_NAME_SHORT = u"SàT"
+    APP_NAME_FILE = u"sat"
+    APP_NAME_FULL = u"{name_short} ({name})".format(
+        name_short=APP_NAME_SHORT, name=APP_NAME
+    )
+    APP_VERSION = (
+        sat.__version__
+    )  # Please add 'D' at the end of version in sat/VERSION for dev versions
+    APP_RELEASE_NAME = u"La Commune"
+    APP_URL = u"https://salut-a-toi.org"
 
     ## Runtime ##
     PLUGIN_EXT = "py"
-    HISTORY_SKIP = u'skip'
+    HISTORY_SKIP = u"skip"
 
     ## Main config ##
-    DEFAULT_BRIDGE = 'dbus'
-
+    DEFAULT_BRIDGE = "dbus"
 
     ## Protocol ##
     XMPP_C2S_PORT = 5222
@@ -53,9 +54,8 @@
     # default port used on Prosody, may differ on other servers
     XMPP_COMPONENT_PORT = 5347
 
-
     ## Parameters ##
-    NO_SECURITY_LIMIT = -1  # FIXME: to rename
+    NO_SECURITY_LIMIT = -1  #  FIXME: to rename
     SECURITY_LIMIT_MAX = 0
     INDIVIDUAL = "individual"
     GENERAL = "general"
@@ -67,9 +67,9 @@
     FORCE_SERVER_PARAM = "Force server"
     FORCE_PORT_PARAM = "Force port"
     # Parameters related to encryption
-    PROFILE_PASS_PATH = ('General', 'Password')
-    MEMORY_CRYPTO_NAMESPACE = 'crypto'  # for the private persistent binary dict
-    MEMORY_CRYPTO_KEY = 'personal_key'
+    PROFILE_PASS_PATH = ("General", "Password")
+    MEMORY_CRYPTO_NAMESPACE = "crypto"  # for the private persistent binary dict
+    MEMORY_CRYPTO_KEY = "personal_key"
     # Parameters for static blog pages
     # FIXME: blog constants should not be in core constants
     STATIC_BLOG_KEY = "Blog page"
@@ -78,7 +78,6 @@
     STATIC_BLOG_PARAM_KEYWORDS = "Keywords"
     STATIC_BLOG_PARAM_DESCRIPTION = "Description"
 
-
     ## Menus ##
     MENU_GLOBAL = "GLOBAL"
     MENU_ROOM = "ROOM"
@@ -88,62 +87,64 @@
     MENU_ROSTER_GROUP_CONTEXT = "MENU_ROSTER_GROUP_CONTEXT"
     MENU_ROOM_OCCUPANT_CONTEXT = "MENU_ROOM_OCCUPANT_CONTEXT"
 
-
     ## Profile and entities ##
-    PROF_KEY_NONE = '@NONE@'
-    PROF_KEY_DEFAULT = '@DEFAULT@'
-    PROF_KEY_ALL = '@ALL@'
-    ENTITY_ALL = '@ALL@'
-    ENTITY_ALL_RESOURCES = '@ALL_RESOURCES@'
-    ENTITY_MAIN_RESOURCE = '@MAIN_RESOURCE@'
-    ENTITY_CAP_HASH = 'CAP_HASH'
-    ENTITY_TYPE = 'TYPE'
-
+    PROF_KEY_NONE = "@NONE@"
+    PROF_KEY_DEFAULT = "@DEFAULT@"
+    PROF_KEY_ALL = "@ALL@"
+    ENTITY_ALL = "@ALL@"
+    ENTITY_ALL_RESOURCES = "@ALL_RESOURCES@"
+    ENTITY_MAIN_RESOURCE = "@MAIN_RESOURCE@"
+    ENTITY_CAP_HASH = "CAP_HASH"
+    ENTITY_TYPE = "TYPE"
 
     ## Roster jids selection ##
-    PUBLIC = 'PUBLIC'
-    ALL = 'ALL' # ALL means all known contacts, while PUBLIC means everybody, known or not
-    GROUP = 'GROUP'
-    JID = 'JID'
-
+    PUBLIC = "PUBLIC"
+    ALL = (
+        "ALL"
+    )  # ALL means all known contacts, while PUBLIC means everybody, known or not
+    GROUP = "GROUP"
+    JID = "JID"
 
     ## Messages ##
-    MESS_TYPE_INFO = 'info'
-    MESS_TYPE_CHAT = 'chat'
-    MESS_TYPE_ERROR = 'error'
-    MESS_TYPE_GROUPCHAT = 'groupchat'
-    MESS_TYPE_HEADLINE = 'headline'
-    MESS_TYPE_NORMAL = 'normal'
-    MESS_TYPE_AUTO = 'auto'  # magic value to let the backend guess the type
-    MESS_TYPE_STANDARD = (MESS_TYPE_CHAT, MESS_TYPE_ERROR, MESS_TYPE_GROUPCHAT, MESS_TYPE_HEADLINE, MESS_TYPE_NORMAL)
+    MESS_TYPE_INFO = "info"
+    MESS_TYPE_CHAT = "chat"
+    MESS_TYPE_ERROR = "error"
+    MESS_TYPE_GROUPCHAT = "groupchat"
+    MESS_TYPE_HEADLINE = "headline"
+    MESS_TYPE_NORMAL = "normal"
+    MESS_TYPE_AUTO = "auto"  # magic value to let the backend guess the type
+    MESS_TYPE_STANDARD = (
+        MESS_TYPE_CHAT,
+        MESS_TYPE_ERROR,
+        MESS_TYPE_GROUPCHAT,
+        MESS_TYPE_HEADLINE,
+        MESS_TYPE_NORMAL,
+    )
     MESS_TYPE_ALL = MESS_TYPE_STANDARD + (MESS_TYPE_INFO, MESS_TYPE_AUTO)
 
     MESS_EXTRA_INFO = "info_type"
 
-
     ## Chat ##
-    CHAT_ONE2ONE = 'one2one'
-    CHAT_GROUP = 'group'
-
+    CHAT_ONE2ONE = "one2one"
+    CHAT_GROUP = "group"
 
     ## Presence ##
-    PRESENCE_UNAVAILABLE = 'unavailable'
-    PRESENCE_SHOW_AWAY = 'away'
-    PRESENCE_SHOW_CHAT = 'chat'
-    PRESENCE_SHOW_DND = 'dnd'
-    PRESENCE_SHOW_XA = 'xa'
-    PRESENCE_SHOW = 'show'
-    PRESENCE_STATUSES = 'statuses'
-    PRESENCE_STATUSES_DEFAULT = 'default'
-    PRESENCE_PRIORITY = 'priority'
-
+    PRESENCE_UNAVAILABLE = "unavailable"
+    PRESENCE_SHOW_AWAY = "away"
+    PRESENCE_SHOW_CHAT = "chat"
+    PRESENCE_SHOW_DND = "dnd"
+    PRESENCE_SHOW_XA = "xa"
+    PRESENCE_SHOW = "show"
+    PRESENCE_STATUSES = "statuses"
+    PRESENCE_STATUSES_DEFAULT = "default"
+    PRESENCE_PRIORITY = "priority"
 
     ## Common namespaces ##
-    NS_XML = 'http://www.w3.org/XML/1998/namespace'
-    NS_CLIENT = 'jabber:client'
-    NS_FORWARD = 'urn:xmpp:forward:0'
-    NS_DELAY = 'urn:xmpp:delay'
-    NS_XHTML = 'http://www.w3.org/1999/xhtml'
+    NS_XML = "http://www.w3.org/XML/1998/namespace"
+    NS_CLIENT = "jabber:client"
+    NS_FORWARD = "urn:xmpp:forward:0"
+    NS_DELAY = "urn:xmpp:delay"
+    NS_XHTML = "http://www.w3.org/1999/xhtml"
 
     ## Common XPath ##
 
@@ -153,23 +154,24 @@
     ## Directories ##
 
     # directory for components specific data
-    COMPONENTS_DIR = u'components'
-    CACHE_DIR = u'cache'
+    COMPONENTS_DIR = u"components"
+    CACHE_DIR = u"cache"
     # files in file dir are stored for long term
     # files dir is global, i.e. for all profiles
-    FILES_DIR = u'files'
+    FILES_DIR = u"files"
     # FILES_LINKS_DIR is a directory where files owned by a specific profile
     # are linked to the global files directory. This way the directory can be
-    # shared per profiles while keeping global directory where identical files
+    #  shared per profiles while keeping global directory where identical files
     # shared between different profiles are not duplicated.
-    FILES_LINKS_DIR = u'files_links'
+    FILES_LINKS_DIR = u"files_links"
     # FILES_TMP_DIR is where profile's partially transfered files are put.
     # Once transfer is completed, they are moved to FILES_DIR
-    FILES_TMP_DIR = u'files_tmp'
-
+    FILES_TMP_DIR = u"files_tmp"
 
     ## Configuration ##
-    if BaseDirectory:  # skipped when xdg module is not available (should not happen in backend)
+    if (
+        BaseDirectory
+    ):  # skipped when xdg module is not available (should not happen in backend)
         if "org.goffi.cagou.cagou" in BaseDirectory.__file__:
             # FIXME: hack to make config read from the right location on Android
             # TODO: fix it in a more proper way
@@ -177,56 +179,71 @@
             # we need to use Android API to get downloads directory
             import os.path
             from jnius import autoclass
+
             Environment = autoclass("android.os.Environment")
 
             BaseDirectory = None
             DEFAULT_CONFIG = {
-                'local_dir': '/data/data/org.goffi.cagou.cagou/app',
-                'media_dir': '/data/data/org.goffi.cagou.cagou/files/app/media',
+                "local_dir": "/data/data/org.goffi.cagou.cagou/app",
+                "media_dir": "/data/data/org.goffi.cagou.cagou/files/app/media",
                 # FIXME: temporary location for downloads, need to call API properly
-                'downloads_dir': os.path.join(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath(), APP_NAME_FILE),
-                'pid_dir': '%(local_dir)s',
-                'log_dir': '%(local_dir)s',
+                "downloads_dir": os.path.join(
+                    Environment.getExternalStoragePublicDirectory(
+                        Environment.DIRECTORY_DOWNLOADS
+                    ).getAbsolutePath(),
+                    APP_NAME_FILE,
+                ),
+                "pid_dir": "%(local_dir)s",
+                "log_dir": "%(local_dir)s",
             }
-            CONFIG_FILES = ['/data/data/org.goffi.cagou.cagou/files/app/android/' + APP_NAME_FILE + '.conf']
+            CONFIG_FILES = [
+                "/data/data/org.goffi.cagou.cagou/files/app/android/"
+                + APP_NAME_FILE
+                + ".conf"
+            ]
         else:
 
             ## Configuration ##
             DEFAULT_CONFIG = {
-                'media_dir': '/usr/share/' + APP_NAME_FILE + '/media',
-                'local_dir': BaseDirectory.save_data_path(APP_NAME_FILE),
-                'downloads_dir': '~/Downloads/' + APP_NAME_FILE,
-                'pid_dir': '%(local_dir)s',
-                'log_dir': '%(local_dir)s',
+                "media_dir": "/usr/share/" + APP_NAME_FILE + "/media",
+                "local_dir": BaseDirectory.save_data_path(APP_NAME_FILE),
+                "downloads_dir": "~/Downloads/" + APP_NAME_FILE,
+                "pid_dir": "%(local_dir)s",
+                "log_dir": "%(local_dir)s",
             }
 
             # List of the configuration filenames sorted by ascending priority
-            CONFIG_FILES = [realpath(expanduser(path) + APP_NAME_FILE + '.conf') for path in
-                            ['/etc/', '~/', '~/.', '', '.'] +
-                            ['%s/' % path for path in list(BaseDirectory.load_config_paths(APP_NAME_FILE))]
-                           ]
+            CONFIG_FILES = [
+                realpath(expanduser(path) + APP_NAME_FILE + ".conf")
+                for path in ["/etc/", "~/", "~/.", "", "."]
+                + [
+                    "%s/" % path
+                    for path in list(BaseDirectory.load_config_paths(APP_NAME_FILE))
+                ]
+            ]
 
     ## Templates ##
-    TEMPLATE_THEME_DEFAULT = u'default'
-    TEMPLATE_STATIC_DIR = u'static'
-
+    TEMPLATE_THEME_DEFAULT = u"default"
+    TEMPLATE_STATIC_DIR = u"static"
 
     ## Plugins ##
 
     # PLUGIN_INFO keys
     # XXX: we use PI instead of PLUG_INFO which would normally be used
     #      to make the header more readable
-    PI_NAME = u'name'
-    PI_IMPORT_NAME = u'import_name'
-    PI_MAIN = u'main'
-    PI_HANDLER = u'handler'
-    PI_TYPE = u'type' # FIXME: should be types, and should handle single unicode type or tuple of types (e.g. "blog" and "import")
-    PI_MODES = u'modes'
-    PI_PROTOCOLS = u'protocols'
-    PI_DEPENDENCIES = u'dependencies'
-    PI_RECOMMENDATIONS = u'recommendations'
-    PI_DESCRIPTION = u'description'
-    PI_USAGE = u'usage'
+    PI_NAME = u"name"
+    PI_IMPORT_NAME = u"import_name"
+    PI_MAIN = u"main"
+    PI_HANDLER = u"handler"
+    PI_TYPE = (
+        u"type"
+    )  #  FIXME: should be types, and should handle single unicode type or tuple of types (e.g. "blog" and "import")
+    PI_MODES = u"modes"
+    PI_PROTOCOLS = u"protocols"
+    PI_DEPENDENCIES = u"dependencies"
+    PI_RECOMMENDATIONS = u"recommendations"
+    PI_DESCRIPTION = u"description"
+    PI_USAGE = u"usage"
 
     # Types
     PLUG_TYPE_XEP = "XEP"
@@ -245,7 +262,7 @@
     PLUG_MODE_BOTH = (PLUG_MODE_CLIENT, PLUG_MODE_COMPONENT)
 
     # names of widely used plugins
-    TEXT_CMDS = 'TEXT-COMMANDS'
+    TEXT_CMDS = "TEXT-COMMANDS"
 
     # PubSub event categories
     PS_PEP = "PEP"
@@ -253,19 +270,18 @@
 
     # PubSub
     PS_PUBLISH = "publish"
-    PS_RETRACT = "retract" # used for items
-    PS_DELETE = "delete" # used for nodes
+    PS_RETRACT = "retract"  # used for items
+    PS_DELETE = "delete"  # used for nodes
     PS_ITEM = "item"
-    PS_ITEMS = "items" # Can contain publish and retract items
+    PS_ITEMS = "items"  # Can contain publish and retract items
     PS_EVENTS = (PS_ITEMS, PS_DELETE)
 
-
     ## XMLUI ##
-    XMLUI_WINDOW = 'window'
-    XMLUI_POPUP = 'popup'
-    XMLUI_FORM = 'form'
-    XMLUI_PARAM = 'param'
-    XMLUI_DIALOG = 'dialog'
+    XMLUI_WINDOW = "window"
+    XMLUI_POPUP = "popup"
+    XMLUI_FORM = "form"
+    XMLUI_PARAM = "param"
+    XMLUI_DIALOG = "dialog"
     XMLUI_DIALOG_CONFIRM = "confirm"
     XMLUI_DIALOG_MESSAGE = "message"
     XMLUI_DIALOG_NOTE = "note"
@@ -284,108 +300,119 @@
     XMLUI_DATA_BTNS_SET_OKCANCEL = "ok/cancel"
     XMLUI_DATA_BTNS_SET_YESNO = "yes/no"
     XMLUI_DATA_BTNS_SET_DEFAULT = XMLUI_DATA_BTNS_SET_OKCANCEL
-    XMLUI_DATA_FILETYPE = 'filetype'
+    XMLUI_DATA_FILETYPE = "filetype"
     XMLUI_DATA_FILETYPE_FILE = "file"
     XMLUI_DATA_FILETYPE_DIR = "dir"
     XMLUI_DATA_FILETYPE_DEFAULT = XMLUI_DATA_FILETYPE_FILE
 
-
     ## Logging ##
-    LOG_LVL_DEBUG = 'DEBUG'
-    LOG_LVL_INFO = 'INFO'
-    LOG_LVL_WARNING = 'WARNING'
-    LOG_LVL_ERROR = 'ERROR'
-    LOG_LVL_CRITICAL = 'CRITICAL'
-    LOG_LEVELS = (LOG_LVL_DEBUG, LOG_LVL_INFO, LOG_LVL_WARNING, LOG_LVL_ERROR, LOG_LVL_CRITICAL)
-    LOG_BACKEND_STANDARD = 'standard'
-    LOG_BACKEND_TWISTED = 'twisted'
-    LOG_BACKEND_BASIC = 'basic'
-    LOG_BACKEND_CUSTOM = 'custom'
-    LOG_BASE_LOGGER = 'root'
-    LOG_TWISTED_LOGGER = 'twisted'
-    LOG_OPT_SECTION = 'DEFAULT' # section of sat.conf where log options should be
-    LOG_OPT_PREFIX = 'log_'
+    LOG_LVL_DEBUG = "DEBUG"
+    LOG_LVL_INFO = "INFO"
+    LOG_LVL_WARNING = "WARNING"
+    LOG_LVL_ERROR = "ERROR"
+    LOG_LVL_CRITICAL = "CRITICAL"
+    LOG_LEVELS = (
+        LOG_LVL_DEBUG,
+        LOG_LVL_INFO,
+        LOG_LVL_WARNING,
+        LOG_LVL_ERROR,
+        LOG_LVL_CRITICAL,
+    )
+    LOG_BACKEND_STANDARD = "standard"
+    LOG_BACKEND_TWISTED = "twisted"
+    LOG_BACKEND_BASIC = "basic"
+    LOG_BACKEND_CUSTOM = "custom"
+    LOG_BASE_LOGGER = "root"
+    LOG_TWISTED_LOGGER = "twisted"
+    LOG_OPT_SECTION = "DEFAULT"  # section of sat.conf where log options should be
+    LOG_OPT_PREFIX = "log_"
     # (option_name, default_value) tuples
-    LOG_OPT_COLORS = ('colors', 'true') # true for auto colors, force to have colors even if stdout is not a tty, false for no color
-    LOG_OPT_TAINTS_DICT = ('levels_taints_dict', {
-        LOG_LVL_DEBUG: ('cyan',),
-        LOG_LVL_INFO: (),
-        LOG_LVL_WARNING: ('yellow',),
-        LOG_LVL_ERROR: ('red', 'blink', r'/!\ ', 'blink_off'),
-        LOG_LVL_CRITICAL: ('bold', 'red', 'Guru Meditation ', 'normal_weight')
-        })
-    LOG_OPT_LEVEL = ('level', 'info')
-    LOG_OPT_FORMAT = ('fmt', '%(message)s') # similar to logging format.
-    LOG_OPT_LOGGER = ('logger', '') # regex to filter logger name
-    LOG_OPT_OUTPUT_SEP = '//'
-    LOG_OPT_OUTPUT_DEFAULT = 'default'
-    LOG_OPT_OUTPUT_MEMORY = 'memory'
+    LOG_OPT_COLORS = (
+        "colors",
+        "true",
+    )  # true for auto colors, force to have colors even if stdout is not a tty, false for no color
+    LOG_OPT_TAINTS_DICT = (
+        "levels_taints_dict",
+        {
+            LOG_LVL_DEBUG: ("cyan",),
+            LOG_LVL_INFO: (),
+            LOG_LVL_WARNING: ("yellow",),
+            LOG_LVL_ERROR: ("red", "blink", r"/!\ ", "blink_off"),
+            LOG_LVL_CRITICAL: ("bold", "red", "Guru Meditation ", "normal_weight"),
+        },
+    )
+    LOG_OPT_LEVEL = ("level", "info")
+    LOG_OPT_FORMAT = ("fmt", "%(message)s")  # similar to logging format.
+    LOG_OPT_LOGGER = ("logger", "")  # regex to filter logger name
+    LOG_OPT_OUTPUT_SEP = "//"
+    LOG_OPT_OUTPUT_DEFAULT = "default"
+    LOG_OPT_OUTPUT_MEMORY = "memory"
     LOG_OPT_OUTPUT_MEMORY_LIMIT = 50
-    LOG_OPT_OUTPUT_FILE = 'file' # file is implicit if only output
-    LOG_OPT_OUTPUT = ('output', LOG_OPT_OUTPUT_SEP + LOG_OPT_OUTPUT_DEFAULT) # //default = normal output (stderr or a file with twistd), path/to/file for a file (must be the first if used), //memory for memory (options can be put in parenthesis, e.g.: //memory(500) for a 500 lines memory)
-
+    LOG_OPT_OUTPUT_FILE = "file"  # file is implicit if only output
+    LOG_OPT_OUTPUT = (
+        "output",
+        LOG_OPT_OUTPUT_SEP + LOG_OPT_OUTPUT_DEFAULT,
+    )  # //default = normal output (stderr or a file with twistd), path/to/file for a file (must be the first if used), //memory for memory (options can be put in parenthesis, e.g.: //memory(500) for a 500 lines memory)
 
     ## action constants ##
     META_TYPE_FILE = "file"
     META_TYPE_OVERWRITE = "overwrite"
 
-
     ## HARD-CODED ACTIONS IDS (generated with uuid.uuid4) ##
-    AUTHENTICATE_PROFILE_ID = u'b03bbfa8-a4ae-4734-a248-06ce6c7cf562'
-    CHANGE_XMPP_PASSWD_ID = u'878b9387-de2b-413b-950f-e424a147bcd0'
-
+    AUTHENTICATE_PROFILE_ID = u"b03bbfa8-a4ae-4734-a248-06ce6c7cf562"
+    CHANGE_XMPP_PASSWD_ID = u"878b9387-de2b-413b-950f-e424a147bcd0"
 
     ## Text values ##
     BOOL_TRUE = "true"
     BOOL_FALSE = "false"
 
-
     ## Special values used in bridge methods calls ##
     HISTORY_LIMIT_DEFAULT = -1
     HISTORY_LIMIT_NONE = -2
 
-
     ## Progress error special values ##
-    PROGRESS_ERROR_DECLINED = u'declined'  # session has been declined by peer user
-
+    PROGRESS_ERROR_DECLINED = u"declined"  #  session has been declined by peer user
 
     ## Files ##
-    FILE_TYPE_DIRECTORY = 'directory'
-    FILE_TYPE_FILE = 'file'
-
+    FILE_TYPE_DIRECTORY = "directory"
+    FILE_TYPE_FILE = "file"
 
     ## Permissions management ##
-    ACCESS_PERM_READ = u'read'
-    ACCESS_PERM_WRITE = u'write'
+    ACCESS_PERM_READ = u"read"
+    ACCESS_PERM_WRITE = u"write"
     ACCESS_PERMS = {ACCESS_PERM_READ, ACCESS_PERM_WRITE}
-    ACCESS_TYPE_PUBLIC = u'public'
-    ACCESS_TYPE_WHITELIST = u'whitelist'
+    ACCESS_TYPE_PUBLIC = u"public"
+    ACCESS_TYPE_WHITELIST = u"whitelist"
     ACCESS_TYPES = (ACCESS_TYPE_PUBLIC, ACCESS_TYPE_WHITELIST)
 
-
     ## Common data keys ##
-    KEY_THUMBNAILS = u'thumbnails'
-    KEY_PROGRESS_ID = u'progress_id'
+    KEY_THUMBNAILS = u"thumbnails"
+    KEY_PROGRESS_ID = u"progress_id"
 
-
-    #internationalisation
-    DEFAULT_LOCALE = u'en_GB'
-
+    # internationalisation
+    DEFAULT_LOCALE = u"en_GB"
 
     ## Misc ##
     SAVEFILE_DATABASE = APP_NAME_FILE + ".db"
     IQ_SET = '/iq[@type="set"]'
-    ENV_PREFIX = 'SAT_' # Prefix used for environment variables
-    IGNORE = 'ignore'
-    NO_LIMIT = -1 # used in bridge when a integer value is expected
-    DEFAULT_MAX_AGE = 1209600 # default max age of cached files, in seconds
-    HASH_SHA1_EMPTY = 'da39a3ee5e6b4b0d3255bfef95601890afd80709'
+    ENV_PREFIX = "SAT_"  # Prefix used for environment variables
+    IGNORE = "ignore"
+    NO_LIMIT = -1  # used in bridge when a integer value is expected
+    DEFAULT_MAX_AGE = 1209600  # default max age of cached files, in seconds
+    HASH_SHA1_EMPTY = "da39a3ee5e6b4b0d3255bfef95601890afd80709"
 
     @classmethod
     def LOG_OPTIONS(cls):
         """Return options checked for logs"""
         # XXX: we use a classmethod so we can use Const inheritance to change default options
-        return(cls.LOG_OPT_COLORS, cls.LOG_OPT_TAINTS_DICT, cls.LOG_OPT_LEVEL, cls.LOG_OPT_FORMAT, cls.LOG_OPT_LOGGER, cls.LOG_OPT_OUTPUT)
+        return (
+            cls.LOG_OPT_COLORS,
+            cls.LOG_OPT_TAINTS_DICT,
+            cls.LOG_OPT_LEVEL,
+            cls.LOG_OPT_FORMAT,
+            cls.LOG_OPT_LOGGER,
+            cls.LOG_OPT_OUTPUT,
+        )
 
     @classmethod
     def bool(cls, value):
--- a/sat/core/exceptions.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/core/exceptions.py	Wed Jun 27 20:14:46 2018 +0200
@@ -84,7 +84,9 @@
     pass
 
 
-class FeatureNotFound(Exception): # a disco feature/identity which is needed is not present
+class FeatureNotFound(
+    Exception
+):  # a disco feature/identity which is needed is not present
     pass
 
 
--- a/sat/core/i18n.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/core/i18n.py	Wed Jun 27 20:14:46 2018 +0200
@@ -19,27 +19,31 @@
 
 
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 
 try:
 
     import gettext
 
-    _ = gettext.translation('sat', 'i18n', fallback=True).ugettext
+    _ = gettext.translation("sat", "i18n", fallback=True).ugettext
     _translators = {None: gettext.NullTranslations()}
 
     def languageSwitch(lang=None):
         if not lang in _translators:
-            _translators[lang] = gettext.translation('sat', languages=[lang], fallback=True)
+            _translators[lang] = gettext.translation(
+                "sat", languages=[lang], fallback=True
+            )
         _translators[lang].install(unicode=True)
 
+
 except ImportError:
 
     log.warning("gettext support disabled")
-    _ = lambda msg: msg # Libervia doesn't support gettext
+    _ = lambda msg: msg  # Libervia doesn't support gettext
+
     def languageSwitch(lang=None):
         pass
 
 
-D_ = lambda msg: msg # used for deferred translations
-
+D_ = lambda msg: msg  # used for deferred translations
--- a/sat/core/log_config.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/core/log_config.py	Wed Jun 27 20:14:46 2018 +0200
@@ -31,6 +31,7 @@
     def __init__(self, *args, **kwargs):
         super(TwistedLogger, self).__init__(*args, **kwargs)
         from twisted.python import log as twisted_log
+
         self.twisted_log = twisted_log
 
     def out(self, message, level=None):
@@ -38,22 +39,30 @@
 
         @param message: formatted message
         """
-        self.twisted_log.msg(message.encode('utf-8', 'ignore'), sat_logged=True, level=level)
+        self.twisted_log.msg(
+            message.encode("utf-8", "ignore"), sat_logged=True, level=level
+        )
 
 
 class ConfigureBasic(log.ConfigureBase):
-
     def configureColors(self, colors, force_colors, levels_taints_dict):
-        super(ConfigureBasic, self).configureColors(colors, force_colors, levels_taints_dict)
+        super(ConfigureBasic, self).configureColors(
+            colors, force_colors, levels_taints_dict
+        )
         if colors:
             import sys
+
             try:
                 isatty = sys.stdout.isatty()
             except AttributeError:
                 isatty = False
-            if force_colors or isatty: # FIXME: isatty should be tested on each handler, not globaly
+            if (
+                force_colors or isatty
+            ):  # FIXME: isatty should be tested on each handler, not globaly
                 # we need colors
-                log.Logger.post_treat = lambda logger, level, message: self.ansiColors(level, message)
+                log.Logger.post_treat = lambda logger, level, message: self.ansiColors(
+                    level, message
+                )
         elif force_colors:
             raise ValueError("force_colors can't be used if colors is False")
 
@@ -61,30 +70,36 @@
     def getProfile():
         """Try to find profile value using introspection"""
         import inspect
+
         stack = inspect.stack()
         current_path = stack[0][1]
         for frame_data in stack[:-1]:
             if frame_data[1] != current_path:
-                if log.backend == C.LOG_BACKEND_STANDARD and "/logging/__init__.py" in frame_data[1]:
+                if (
+                    log.backend == C.LOG_BACKEND_STANDARD
+                    and "/logging/__init__.py" in frame_data[1]
+                ):
                     continue
                 break
 
         frame = frame_data[0]
         args = inspect.getargvalues(frame)
         try:
-            profile = args.locals.get('profile') or args.locals['profile_key']
+            profile = args.locals.get("profile") or args.locals["profile_key"]
         except (TypeError, KeyError):
             try:
                 try:
-                    profile = args.locals['self'].profile
+                    profile = args.locals["self"].profile
                 except AttributeError:
                     try:
-                        profile = args.locals['self'].parent.profile
+                        profile = args.locals["self"].parent.profile
                     except AttributeError:
-                        profile = args.locals['self'].host.profile # used in quick_frontend for single profile configuration
+                        profile = args.locals[
+                            "self"
+                        ].host.profile  # used in quick_frontend for single profile configuration
             except Exception:
                 # we can't find profile, we return an empty value
-                profile = ''
+                profile = ""
         return profile
 
 
@@ -97,16 +112,23 @@
         @param observer: original observer to hook
         @param can_colors: True if observer can display ansi colors
         """
+
         def observer_hook(event):
             """redirect non SàT log to twisted_logger, and add colors when possible"""
-            if 'sat_logged' in event: # we only want our own logs, other are managed by twistedObserver
+            if (
+                "sat_logged" in event
+            ):  # we only want our own logs, other are managed by twistedObserver
                 # we add colors if possible
-                if (can_colors and self.LOGGER_CLASS.colors) or self.LOGGER_CLASS.force_colors:
-                    message = event.get('message', tuple())
-                    level = event.get('level', C.LOG_LVL_INFO)
+                if (
+                    can_colors and self.LOGGER_CLASS.colors
+                ) or self.LOGGER_CLASS.force_colors:
+                    message = event.get("message", tuple())
+                    level = event.get("level", C.LOG_LVL_INFO)
                     if message:
-                        event['message'] = (self.ansiColors(level, ''.join(message)),) # must be a tuple
-                observer(event) # we can now call the original observer
+                        event["message"] = (
+                            self.ansiColors(level, "".join(message)),
+                        )  # must be a tuple
+                observer(event)  # we can now call the original observer
 
         return observer_hook
 
@@ -130,7 +152,7 @@
         @param observer: observer to hook
         @return: hooked observer or original one
         """
-        if hasattr(observer, '__self__'):
+        if hasattr(observer, "__self__"):
             ori = observer
             if isinstance(observer.__self__, self.twisted_log.FileLogObserver):
                 observer = self.changeFileLogObserver(observer)
@@ -147,12 +169,15 @@
         """initialise needed attributes, and install observers hooks"""
         self.observers = {}
         from twisted.python import log as twisted_log
+
         self.twisted_log = twisted_log
         self.log_publisher = twisted_log.msg.__self__
+
         def addObserverObserver(self_logpub, other):
             """Install hook so we know when a new observer is added"""
             other = self.installObserverHook(other)
             return self_logpub._originalAddObserver(other)
+
         def removeObserverObserver(self_logpub, ori):
             """removeObserver hook fix
 
@@ -170,11 +195,20 @@
                         raise ValueError("Unknown observer")
 
         # we replace addObserver/removeObserver by our own
-        twisted_log.LogPublisher._originalAddObserver = twisted_log.LogPublisher.addObserver
-        twisted_log.LogPublisher._originalRemoveObserver = twisted_log.LogPublisher.removeObserver
-        import types # see https://stackoverflow.com/a/4267590 (thx Chris Morgan/aaronasterling)
-        twisted_log.addObserver = types.MethodType(addObserverObserver, self.log_publisher, twisted_log.LogPublisher)
-        twisted_log.removeObserver = types.MethodType(removeObserverObserver, self.log_publisher, twisted_log.LogPublisher)
+        twisted_log.LogPublisher._originalAddObserver = (
+            twisted_log.LogPublisher.addObserver
+        )
+        twisted_log.LogPublisher._originalRemoveObserver = (
+            twisted_log.LogPublisher.removeObserver
+        )
+        import types  # see https://stackoverflow.com/a/4267590 (thx Chris Morgan/aaronasterling)
+
+        twisted_log.addObserver = types.MethodType(
+            addObserverObserver, self.log_publisher, twisted_log.LogPublisher
+        )
+        twisted_log.removeObserver = types.MethodType(
+            removeObserverObserver, self.log_publisher, twisted_log.LogPublisher
+        )
 
         # we now change existing observers
         for idx, observer in enumerate(self.log_publisher.observers):
@@ -186,6 +220,7 @@
 
     def configureOutput(self, output):
         import sys
+
         if output is None:
             output = C.LOG_OPT_OUTPUT_SEP + C.LOG_OPT_OUTPUT_DEFAULT
         self.manageOutputs(output)
@@ -194,9 +229,11 @@
         if C.LOG_OPT_OUTPUT_DEFAULT in log.handlers:
             # default output is already managed, we just add output to stdout if we are in debug or nodaemon mode
             if self.backend_data is None:
-                raise ValueError("You must pass options as backend_data with Twisted backend")
+                raise ValueError(
+                    "You must pass options as backend_data with Twisted backend"
+                )
             options = self.backend_data
-            if options.get('nodaemon', False) or options.get('debug', False):
+            if options.get("nodaemon", False) or options.get("debug", False):
                 addObserver(self.twisted_log.FileLogObserver(sys.stdout).emit)
         else:
             # \\default is not in the output, so we remove current observers
@@ -208,49 +245,81 @@
 
         if C.LOG_OPT_OUTPUT_FILE in log.handlers:
             from twisted.python import logfile
+
             for path in log.handlers[C.LOG_OPT_OUTPUT_FILE]:
-                log_file = sys.stdout if path == '-' else logfile.LogFile.fromFullPath(path)
+                log_file = (
+                    sys.stdout if path == "-" else logfile.LogFile.fromFullPath(path)
+                )
                 addObserver(self.twisted_log.FileLogObserver(log_file).emit)
 
         if C.LOG_OPT_OUTPUT_MEMORY in log.handlers:
-            raise NotImplementedError("Memory observer is not implemented in Twisted backend")
+            raise NotImplementedError(
+                "Memory observer is not implemented in Twisted backend"
+            )
 
     def configureColors(self, colors, force_colors, levels_taints_dict):
-        super(ConfigureTwisted, self).configureColors(colors, force_colors, levels_taints_dict)
+        super(ConfigureTwisted, self).configureColors(
+            colors, force_colors, levels_taints_dict
+        )
         self.LOGGER_CLASS.colors = colors
         self.LOGGER_CLASS.force_colors = force_colors
         if force_colors and not colors:
-            raise ValueError('colors must be True if force_colors is True')
+            raise ValueError("colors must be True if force_colors is True")
 
     def postTreatment(self):
         """Install twistedObserver which manage non SàT logs"""
+
         def twistedObserver(event):
             """Observer which redirect log message not produced by SàT to SàT logging system"""
-            if not 'sat_logged' in event:
+            if not "sat_logged" in event:
                 # this log was not produced by SàT
                 from twisted.python import log as twisted_log
+
                 text = twisted_log.textFromEventDict(event)
                 if text is None:
                     return
                 twisted_logger = log.getLogger(C.LOG_TWISTED_LOGGER)
-                log_method = twisted_logger.error if event.get('isError', False) else twisted_logger.info
-                log_method(text.decode('utf-8'))
+                log_method = (
+                    twisted_logger.error
+                    if event.get("isError", False)
+                    else twisted_logger.info
+                )
+                log_method(text.decode("utf-8"))
 
         self.log_publisher._originalAddObserver(twistedObserver)
 
 
 class ConfigureStandard(ConfigureBasic):
-
-    def __init__(self, level=None, fmt=None, output=None, logger=None, colors=False, levels_taints_dict=None, force_colors=False, backend_data=None):
+    def __init__(
+        self,
+        level=None,
+        fmt=None,
+        output=None,
+        logger=None,
+        colors=False,
+        levels_taints_dict=None,
+        force_colors=False,
+        backend_data=None,
+    ):
         if fmt is None:
             fmt = C.LOG_OPT_FORMAT[1]
         if output is None:
             output = C.LOG_OPT_OUTPUT[1]
-        super(ConfigureStandard, self).__init__(level, fmt, output, logger, colors, levels_taints_dict, force_colors, backend_data)
+        super(ConfigureStandard, self).__init__(
+            level,
+            fmt,
+            output,
+            logger,
+            colors,
+            levels_taints_dict,
+            force_colors,
+            backend_data,
+        )
 
     def preTreatment(self):
         """We use logging methods directly, instead of using Logger"""
         import logging
+
         log.getLogger = logging.getLogger
         log.debug = logging.debug
         log.info = logging.info
@@ -271,7 +340,7 @@
         class SatFormatter(logging.Formatter):
             u"""Formatter which manage SàT specificities"""
             _format = fmt
-            _with_profile = '%(profile)s' in fmt
+            _with_profile = "%(profile)s" in fmt
 
             def __init__(self, can_colors=False):
                 super(SatFormatter, self).__init__(self._format)
@@ -288,14 +357,14 @@
                         record.color_start = log.COLOR_START
                         record.color_end = log.COLOR_END
                     else:
-                        record.color_start = record.color_end = ''
+                        record.color_start = record.color_end = ""
                 s = super(SatFormatter, self).format(record)
                 if do_color:
                     s = ConfigureStandard.ansiColors(record.levelname, s)
                 if sys.platform == "android":
                     # FIXME: dirty hack to workaround android encoding issue on log
                     #        need to be fixed properly
-                    return s.encode('ascii', 'ignore')
+                    return s.encode("ascii", "ignore")
                 else:
                     return s
 
@@ -308,7 +377,9 @@
         self.name_filter = log.FilterName(logger) if logger else None
 
     def configureColors(self, colors, force_colors, levels_taints_dict):
-        super(ConfigureStandard, self).configureColors(colors, force_colors, levels_taints_dict)
+        super(ConfigureStandard, self).configureColors(
+            colors, force_colors, levels_taints_dict
+        )
         self.formatterClass.with_colors = colors
         self.formatterClass.force_colors = force_colors
         if not colors and force_colors:
@@ -323,6 +394,7 @@
 
     def postTreatment(self):
         import logging
+
         root_logger = logging.getLogger()
         if len(root_logger.handlers) == 0:
             for handler, options in log.handlers.items():
@@ -335,14 +407,21 @@
                     self._addHandler(root_logger, hdlr, can_colors=can_colors)
                 elif handler == C.LOG_OPT_OUTPUT_MEMORY:
                     from logging.handlers import BufferingHandler
+
                     class SatMemoryHandler(BufferingHandler):
                         def emit(self, record):
                             super(SatMemoryHandler, self).emit(self.format(record))
+
                     hdlr = SatMemoryHandler(options)
-                    log.handlers[handler] = hdlr # we keep a reference to the handler to read the buffer later
+                    log.handlers[
+                        handler
+                    ] = (
+                        hdlr
+                    )  # we keep a reference to the handler to read the buffer later
                     self._addHandler(root_logger, hdlr, can_colors=False)
                 elif handler == C.LOG_OPT_OUTPUT_FILE:
                     import os.path
+
                     for path in options:
                         hdlr = logging.FileHandler(os.path.expanduser(path))
                         self._addHandler(root_logger, hdlr, can_colors=False)
@@ -358,13 +437,16 @@
         @param size: number of logs to return
         """
         mem_handler = log.handlers[C.LOG_OPT_OUTPUT_MEMORY]
-        return (log_msg for log_msg in mem_handler.buffer[size if size is None else -size:])
+        return (
+            log_msg for log_msg in mem_handler.buffer[size if size is None else -size :]
+        )
 
 
 log.configure_cls[C.LOG_BACKEND_BASIC] = ConfigureBasic
 log.configure_cls[C.LOG_BACKEND_TWISTED] = ConfigureTwisted
 log.configure_cls[C.LOG_BACKEND_STANDARD] = ConfigureStandard
 
+
 def configure(backend, **options):
     """Configure logging behaviour
     @param backend: can be:
@@ -375,6 +457,7 @@
     """
     return log.configure(backend, **options)
 
+
 def _parseOptions(options):
     """Parse string options as given in conf or environment variable, and return expected python value
 
@@ -384,11 +467,11 @@
     LEVEL = C.LOG_OPT_LEVEL[0]
 
     if COLORS in options:
-        if options[COLORS].lower() in ('1', 'true'):
+        if options[COLORS].lower() in ("1", "true"):
             options[COLORS] = True
-        elif options[COLORS] == 'force':
+        elif options[COLORS] == "force":
             options[COLORS] = True
-            options['force_colors'] = True
+            options["force_colors"] = True
         else:
             options[COLORS] = False
     if LEVEL in options:
@@ -397,6 +480,7 @@
             level = C.LOG_LVL_INFO
         options[LEVEL] = level
 
+
 def satConfigure(backend=C.LOG_BACKEND_STANDARD, const=None, backend_data=None):
     """Configure logging system for SàT, can be used by frontends
 
@@ -413,13 +497,18 @@
         log.C = const
     from sat.tools import config
     import os
+
     log_conf = {}
     sat_conf = config.parseMainConf()
     for opt_name, opt_default in C.LOG_OPTIONS():
         try:
-            log_conf[opt_name] = os.environ[''.join((C.ENV_PREFIX, C.LOG_OPT_PREFIX.upper(), opt_name.upper()))]
+            log_conf[opt_name] = os.environ[
+                "".join((C.ENV_PREFIX, C.LOG_OPT_PREFIX.upper(), opt_name.upper()))
+            ]
         except KeyError:
-            log_conf[opt_name] = config.getConfig(sat_conf, C.LOG_OPT_SECTION, C.LOG_OPT_PREFIX + opt_name, opt_default)
+            log_conf[opt_name] = config.getConfig(
+                sat_conf, C.LOG_OPT_SECTION, C.LOG_OPT_PREFIX + opt_name, opt_default
+            )
 
     _parseOptions(log_conf)
     configure(backend, backend_data=backend_data, **log_conf)
--- a/sat/core/sat_main.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/core/sat_main.py	Wed Jun 27 20:14:46 2018 +0200
@@ -27,6 +27,7 @@
 from sat.core import xmpp
 from sat.core import exceptions
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from sat.core.constants import Const as C
 from sat.memory import memory
@@ -43,26 +44,31 @@
 import uuid
 
 try:
-    from collections import OrderedDict # only available from python 2.7
+    from collections import OrderedDict  # only available from python 2.7
 except ImportError:
     from ordereddict import OrderedDict
 
 
 class SAT(service.Service):
-
     def __init__(self):
         self._cb_map = {}  # map from callback_id to callbacks
-        self._menus = OrderedDict()  # dynamic menus. key: callback_id, value: menu data (dictionnary)
+        self._menus = (
+            OrderedDict()
+        )  # dynamic menus. key: callback_id, value: menu data (dictionnary)
         self._menus_paths = {}  # path to id. key: (menu_type, lower case tuple of path), value: menu id
         self.initialised = defer.Deferred()
         self.profiles = {}
         self.plugins = {}
-        self.ns_map = {u'x-data': u'jabber:x:data'}  # map for short name to whole namespace,
-                                                      # extended by plugins with registerNamespace
+        self.ns_map = {
+            u"x-data": u"jabber:x:data"
+        }  #  map for short name to whole namespace,
+        #  extended by plugins with registerNamespace
         self.memory = memory.Memory(self)
-        self.trigger = trigger.TriggerManager()  # trigger are used to change SàT behaviour
+        self.trigger = (
+            trigger.TriggerManager()
+        )  # trigger are used to change SàT behaviour
 
-        bridge_name = self.memory.getConfig('', 'bridge', 'dbus')
+        bridge_name = self.memory.getConfig("", "bridge", "dbus")
 
         bridge_module = dynamic_import.bridge(bridge_name)
         if bridge_module is None:
@@ -79,28 +85,42 @@
         self.bridge.register_method("getFeatures", self.getFeatures)
         self.bridge.register_method("profileNameGet", self.memory.getProfileName)
         self.bridge.register_method("profilesListGet", self.memory.getProfilesList)
-        self.bridge.register_method("getEntityData", lambda jid_, keys, profile: self.memory.getEntityData(jid.JID(jid_), keys, profile))
+        self.bridge.register_method(
+            "getEntityData",
+            lambda jid_, keys, profile: self.memory.getEntityData(
+                jid.JID(jid_), keys, profile
+            ),
+        )
         self.bridge.register_method("getEntitiesData", self.memory._getEntitiesData)
         self.bridge.register_method("profileCreate", self.memory.createProfile)
         self.bridge.register_method("asyncDeleteProfile", self.memory.asyncDeleteProfile)
         self.bridge.register_method("profileStartSession", self.memory.startSession)
-        self.bridge.register_method("profileIsSessionStarted", self.memory._isSessionStarted)
+        self.bridge.register_method(
+            "profileIsSessionStarted", self.memory._isSessionStarted
+        )
         self.bridge.register_method("profileSetDefault", self.memory.profileSetDefault)
         self.bridge.register_method("connect", self._connect)
         self.bridge.register_method("disconnect", self.disconnect)
         self.bridge.register_method("getContacts", self.getContacts)
         self.bridge.register_method("getContactsFromGroup", self.getContactsFromGroup)
         self.bridge.register_method("getMainResource", self.memory._getMainResource)
-        self.bridge.register_method("getPresenceStatuses", self.memory._getPresenceStatuses)
+        self.bridge.register_method(
+            "getPresenceStatuses", self.memory._getPresenceStatuses
+        )
         self.bridge.register_method("getWaitingSub", self.memory.getWaitingSub)
         self.bridge.register_method("messageSend", self._messageSend)
         self.bridge.register_method("getConfig", self._getConfig)
         self.bridge.register_method("setParam", self.setParam)
         self.bridge.register_method("getParamA", self.memory.getStringParamA)
         self.bridge.register_method("asyncGetParamA", self.memory.asyncGetStringParamA)
-        self.bridge.register_method("asyncGetParamsValuesFromCategory", self.memory.asyncGetParamsValuesFromCategory)
+        self.bridge.register_method(
+            "asyncGetParamsValuesFromCategory",
+            self.memory.asyncGetParamsValuesFromCategory,
+        )
         self.bridge.register_method("getParamsUI", self.memory.getParamsUI)
-        self.bridge.register_method("getParamsCategories", self.memory.getParamsCategories)
+        self.bridge.register_method(
+            "getParamsCategories", self.memory.getParamsCategories
+        )
         self.bridge.register_method("paramsRegisterApp", self.memory.paramsRegisterApp)
         self.bridge.register_method("historyGet", self.memory._historyGet)
         self.bridge.register_method("setPresence", self._setPresence)
@@ -135,12 +155,14 @@
     def full_version(self):
         """Return the full version of SàT (with release name and extra data when in development mode)"""
         version = self.version
-        if version[-1] == 'D':
+        if version[-1] == "D":
             # we are in debug version, we add extra data
             try:
                 return self._version_cache
             except AttributeError:
-                self._version_cache = u"{} « {} » ({})".format(version, C.APP_RELEASE_NAME, utils.getRepositoryData(sat))
+                self._version_cache = u"{} « {} » ({})".format(
+                    version, C.APP_RELEASE_NAME, utils.getRepositoryData(sat)
+                )
                 return self._version_cache
         else:
             return version
@@ -158,8 +180,11 @@
             ui_contact_list.ContactList(self)
             ui_profile_manager.ProfileManager(self)
         except Exception as e:
-            log.error(_(u"Could not initialize backend: {reason}").format(
-                reason = str(e).decode('utf-8', 'ignore')))
+            log.error(
+                _(u"Could not initialize backend: {reason}").format(
+                    reason=str(e).decode("utf-8", "ignore")
+                )
+            )
             sys.exit(1)
         self.initialised.callback(None)
         log.info(_(u"Backend is ready"))
@@ -180,43 +205,69 @@
         #       just use a client, and plugin blacklisting should be possible in sat.conf
         plugins_path = os.path.dirname(sat.plugins.__file__)
         plugin_glob = "plugin*." + C.PLUGIN_EXT
-        plug_lst = [os.path.splitext(plugin)[0] for plugin in map(os.path.basename, glob(os.path.join(plugins_path, plugin_glob)))]
+        plug_lst = [
+            os.path.splitext(plugin)[0]
+            for plugin in map(
+                os.path.basename, glob(os.path.join(plugins_path, plugin_glob))
+            )
+        ]
         plugins_to_import = {}  # plugins we still have to import
         for plug in plug_lst:
-            plugin_path = 'sat.plugins.' + plug
+            plugin_path = "sat.plugins." + plug
             try:
                 __import__(plugin_path)
             except exceptions.MissingModule as e:
                 self._unimport_plugin(plugin_path)
-                log.warning(u"Can't import plugin [{path}] because of an unavailale third party module:\n{msg}".format(
-                    path=plugin_path, msg=e))
+                log.warning(
+                    u"Can't import plugin [{path}] because of an unavailale third party module:\n{msg}".format(
+                        path=plugin_path, msg=e
+                    )
+                )
                 continue
             except exceptions.CancelError as e:
-                log.info(u"Plugin [{path}] cancelled its own import: {msg}".format(path=plugin_path, msg=e))
+                log.info(
+                    u"Plugin [{path}] cancelled its own import: {msg}".format(
+                        path=plugin_path, msg=e
+                    )
+                )
                 self._unimport_plugin(plugin_path)
                 continue
             except Exception as e:
                 import traceback
-                log.error(_(u"Can't import plugin [{path}]:\n{error}").format(path=plugin_path, error=traceback.format_exc()))
+
+                log.error(
+                    _(u"Can't import plugin [{path}]:\n{error}").format(
+                        path=plugin_path, error=traceback.format_exc()
+                    )
+                )
                 self._unimport_plugin(plugin_path)
                 continue
             mod = sys.modules[plugin_path]
             plugin_info = mod.PLUGIN_INFO
-            import_name = plugin_info['import_name']
+            import_name = plugin_info["import_name"]
 
-            plugin_modes = plugin_info[u'modes'] = set(plugin_info.setdefault(u"modes", C.PLUG_MODE_DEFAULT))
+            plugin_modes = plugin_info[u"modes"] = set(
+                plugin_info.setdefault(u"modes", C.PLUG_MODE_DEFAULT)
+            )
 
             # if the plugin is an entry point, it must work in component mode
-            if plugin_info[u'type'] == C.PLUG_TYPE_ENTRY_POINT:
+            if plugin_info[u"type"] == C.PLUG_TYPE_ENTRY_POINT:
                 # if plugin is an entrypoint, we cache it
                 if C.PLUG_MODE_COMPONENT not in plugin_modes:
-                    log.error(_(u"{type} type must be used with {mode} mode, ignoring plugin").format(
-                        type = C.PLUG_TYPE_ENTRY_POINT, mode = C.PLUG_MODE_COMPONENT))
+                    log.error(
+                        _(
+                            u"{type} type must be used with {mode} mode, ignoring plugin"
+                        ).format(type=C.PLUG_TYPE_ENTRY_POINT, mode=C.PLUG_MODE_COMPONENT)
+                    )
                     self._unimport_plugin(plugin_path)
                     continue
 
             if import_name in plugins_to_import:
-                log.error(_(u"Name conflict for import name [{import_name}], can't import plugin [{name}]").format(**plugin_info))
+                log.error(
+                    _(
+                        u"Name conflict for import name [{import_name}], can't import plugin [{name}]"
+                    ).format(**plugin_info)
+                )
                 continue
             plugins_to_import[import_name] = (plugin_path, mod, plugin_info)
         while True:
@@ -227,7 +278,9 @@
             if not plugins_to_import:
                 break
 
-    def _import_plugins_from_dict(self, plugins_to_import, import_name=None, optional=False):
+    def _import_plugins_from_dict(
+        self, plugins_to_import, import_name=None, optional=False
+    ):
         """Recursively import and their dependencies in the right order
 
         @param plugins_to_import(dict): key=import_name and values=(plugin_path, module, plugin_info)
@@ -235,14 +288,16 @@
         @param optional(bool): if False and plugin is not found, an ImportError exception is raised
         """
         if import_name in self.plugins:
-            log.debug(u'Plugin {} already imported, passing'.format(import_name))
+            log.debug(u"Plugin {} already imported, passing".format(import_name))
             return
         if not import_name:
             import_name, (plugin_path, mod, plugin_info) = plugins_to_import.popitem()
         else:
             if not import_name in plugins_to_import:
                 if optional:
-                    log.warning(_(u"Recommended plugin not found: {}").format(import_name))
+                    log.warning(
+                        _(u"Recommended plugin not found: {}").format(import_name)
+                    )
                     return
                 msg = u"Dependency not found: {}".format(import_name)
                 log.error(msg)
@@ -252,21 +307,33 @@
         recommendations = plugin_info.setdefault("recommendations", [])
         for to_import in dependencies + recommendations:
             if to_import not in self.plugins:
-                log.debug(u'Recursively import dependency of [%s]: [%s]' % (import_name, to_import))
+                log.debug(
+                    u"Recursively import dependency of [%s]: [%s]"
+                    % (import_name, to_import)
+                )
                 try:
-                    self._import_plugins_from_dict(plugins_to_import, to_import, to_import not in dependencies)
+                    self._import_plugins_from_dict(
+                        plugins_to_import, to_import, to_import not in dependencies
+                    )
                 except ImportError as e:
-                    log.warning(_(u"Can't import plugin {name}: {error}").format(name=plugin_info['name'], error=e))
+                    log.warning(
+                        _(u"Can't import plugin {name}: {error}").format(
+                            name=plugin_info["name"], error=e
+                        )
+                    )
                     if optional:
                         return
                     raise e
-        log.info("importing plugin: {}".format(plugin_info['name']))
+        log.info("importing plugin: {}".format(plugin_info["name"]))
         # we instanciate the plugin here
         try:
-            self.plugins[import_name] = getattr(mod, plugin_info['main'])(self)
+            self.plugins[import_name] = getattr(mod, plugin_info["main"])(self)
         except Exception as e:
-            log.warning(u'Error while loading plugin "{name}", ignoring it: {error}'
-                .format(name=plugin_info['name'], error=e))
+            log.warning(
+                u'Error while loading plugin "{name}", ignoring it: {error}'.format(
+                    name=plugin_info["name"], error=e
+                )
+            )
             if optional:
                 return
             raise ImportError(u"Error during initiation")
@@ -276,7 +343,7 @@
             self.plugins[import_name].is_handler = False
         # we keep metadata as a Class attribute
         self.plugins[import_name]._info = plugin_info
-        #TODO: test xmppclient presence and register handler parent
+        # TODO: test xmppclient presence and register handler parent
 
     def pluginsUnload(self):
         """Call unload method on every loaded plugin, if exists
@@ -296,11 +363,11 @@
                 defers_list.append(defer.maybeDeferred(unload))
         return defers_list
 
-    def _connect(self, profile_key, password='', options=None):
+    def _connect(self, profile_key, password="", options=None):
         profile = self.memory.getProfileName(profile_key)
         return self.connect(profile, password, options)
 
-    def connect(self, profile, password='', options=None, max_retries=C.XMPP_MAX_RETRIES):
+    def connect(self, profile, password="", options=None, max_retries=C.XMPP_MAX_RETRIES):
         """Connect a profile (i.e. connect client.component to XMPP server)
 
         Retrieve the individual parameters, authenticate the profile
@@ -316,7 +383,8 @@
         @raise exceptions.PasswordError: Profile password is wrong
         """
         if options is None:
-            options={}
+            options = {}
+
         def connectProfile(dummy=None):
             if self.isConnected(profile):
                 log.info(_("already connected !"))
@@ -375,15 +443,19 @@
             features.append(features_d)
 
         d_list = defer.DeferredList(features)
+
         def buildFeatures(result, import_names):
             assert len(result) == len(import_names)
             ret = {}
-            for name, (success, data) in zip (import_names, result):
+            for name, (success, data) in zip(import_names, result):
                 if success:
                     ret[name] = data
                 else:
-                    log.warning(u"Error while getting features for {name}: {failure}".format(
-                        name=name, failure=data))
+                    log.warning(
+                        u"Error while getting features for {name}: {failure}".format(
+                            name=name, failure=data
+                        )
+                    )
                     ret[name] = {}
             return ret
 
@@ -392,6 +464,7 @@
 
     def getContacts(self, profile_key):
         client = self.getClient(profile_key)
+
         def got_roster(dummy):
             ret = []
             for item in client.roster.getItems():  # we get all items for client's roster
@@ -467,14 +540,14 @@
         @return: list of clients
         """
         if not profile_key:
-            raise exceptions.DataError(_(u'profile_key must not be empty'))
+            raise exceptions.DataError(_(u"profile_key must not be empty"))
         try:
             profile = self.memory.getProfileName(profile_key, True)
         except exceptions.ProfileUnknownError:
             return []
         if profile == C.PROF_KEY_ALL:
             return self.profiles.values()
-        elif profile[0] == '@':  # only profile keys can start with "@"
+        elif profile[0] == "@":  #  only profile keys can start with "@"
             raise exceptions.ProfileKeyUnknown
         return [self.profiles[profile]]
 
@@ -485,7 +558,7 @@
         @param name: name of the option
         @return: unicode representation of the option
         """
-        return unicode(self.memory.getConfig(section, name, ''))
+        return unicode(self.memory.getConfig(section, name, ""))
 
     def logErrback(self, failure_):
         """generic errback logging
@@ -495,12 +568,12 @@
         log.error(_(u"Unexpected error: {}".format(failure_)))
         return failure_
 
-    # namespaces
+    #  namespaces
 
     def registerNamespace(self, short_name, namespace):
         """associate a namespace to a short name"""
         if short_name in self.ns_map:
-            raise exceptions.ConflictError(u'this short name is already used')
+            raise exceptions.ConflictError(u"this short name is already used")
         self.ns_map[short_name] = namespace
 
     def getNamespaces(self):
@@ -509,10 +582,7 @@
     def getSessionInfos(self, profile_key):
         """compile interesting data on current profile session"""
         client = self.getClient(profile_key)
-        data = {
-            "jid": client.jid.full(),
-            "started": unicode(int(client.started)),
-            }
+        data = {"jid": client.jid.full(), "started": unicode(int(client.started))}
         return defer.succeed(data)
 
     # local dirs
@@ -531,11 +601,11 @@
         """
         # FIXME: component and profile are parsed with **kwargs because of python 2 limitations
         #        once moved to python 3, this can be fixed
-        component = kwargs.pop('component', False)
-        profile = kwargs.pop('profile', True)
+        component = kwargs.pop("component", False)
+        profile = kwargs.pop("profile", True)
         assert not kwargs
 
-        path_elts = [self.memory.getConfig('', 'local_dir')]
+        path_elts = [self.memory.getConfig("", "local_dir")]
         if component:
             path_elts.append(C.COMPONENTS_DIR)
         path_elts.append(regex.pathEscape(dir_name))
@@ -561,7 +631,7 @@
         """
         profile = self.memory.getProfileName(profile_key)
         if not profile:
-            log.error(_('asking connection status for a non-existant profile'))
+            log.error(_("asking connection status for a non-existant profile"))
             raise exceptions.ProfileUnknownError(profile_key)
         if profile not in self.profiles:
             return False
@@ -569,28 +639,47 @@
 
     ## XMPP methods ##
 
-    def _messageSend(self, to_jid_s, message, subject=None, mess_type='auto', extra=None, profile_key=C.PROF_KEY_NONE):
+    def _messageSend(
+        self,
+        to_jid_s,
+        message,
+        subject=None,
+        mess_type="auto",
+        extra=None,
+        profile_key=C.PROF_KEY_NONE,
+    ):
         client = self.getClient(profile_key)
         to_jid = jid.JID(to_jid_s)
-        #XXX: we need to use the dictionary comprehension because D-Bus return its own types, and pickle can't manage them. TODO: Need to find a better way
-        return client.sendMessage(to_jid, message, subject, mess_type, {unicode(key): unicode(value) for key, value in extra.items()})
+        # XXX: we need to use the dictionary comprehension because D-Bus return its own types, and pickle can't manage them. TODO: Need to find a better way
+        return client.sendMessage(
+            to_jid,
+            message,
+            subject,
+            mess_type,
+            {unicode(key): unicode(value) for key, value in extra.items()},
+        )
 
     def _setPresence(self, to="", show="", statuses=None, profile_key=C.PROF_KEY_NONE):
         return self.setPresence(jid.JID(to) if to else None, show, statuses, profile_key)
 
-    def setPresence(self, to_jid=None, show="", statuses=None, profile_key=C.PROF_KEY_NONE):
+    def setPresence(
+        self, to_jid=None, show="", statuses=None, profile_key=C.PROF_KEY_NONE
+    ):
         """Send our presence information"""
         if statuses is None:
             statuses = {}
         profile = self.memory.getProfileName(profile_key)
         assert profile
-        priority = int(self.memory.getParamA("Priority", "Connection", profile_key=profile))
+        priority = int(
+            self.memory.getParamA("Priority", "Connection", profile_key=profile)
+        )
         self.profiles[profile].presence.available(to_jid, show, statuses, priority)
-        #XXX: FIXME: temporary fix to work around openfire 3.7.0 bug (presence is not broadcasted to generating resource)
-        if '' in statuses:
-            statuses[C.PRESENCE_STATUSES_DEFAULT] = statuses.pop('')
-        self.bridge.presenceUpdate(self.profiles[profile].jid.full(), show,
-                                   int(priority), statuses, profile)
+        # XXX: FIXME: temporary fix to work around openfire 3.7.0 bug (presence is not broadcasted to generating resource)
+        if "" in statuses:
+            statuses[C.PRESENCE_STATUSES_DEFAULT] = statuses.pop("")
+        self.bridge.presenceUpdate(
+            self.profiles[profile].jid.full(), show, int(priority), statuses, profile
+        )
 
     def subscription(self, subs_type, raw_jid, profile_key):
         """Called to manage subscription
@@ -600,7 +689,10 @@
         profile = self.memory.getProfileName(profile_key)
         assert profile
         to_jid = jid.JID(raw_jid)
-        log.debug(_(u'subsciption request [%(subs_type)s] for %(jid)s') % {'subs_type': subs_type, 'jid': to_jid.full()})
+        log.debug(
+            _(u"subsciption request [%(subs_type)s] for %(jid)s")
+            % {"subs_type": subs_type, "jid": to_jid.full()}
+        )
         if subs_type == "subscribe":
             self.profiles[profile].presence.subscribe(to_jid)
         elif subs_type == "subscribed":
@@ -671,12 +763,41 @@
     def findFeaturesSet(self, *args, **kwargs):
         return self.memory.disco.findFeaturesSet(*args, **kwargs)
 
-    def _findByFeatures(self, namespaces, identities, bare_jids, service, roster, own_jid, local_device, profile_key):
+    def _findByFeatures(
+        self,
+        namespaces,
+        identities,
+        bare_jids,
+        service,
+        roster,
+        own_jid,
+        local_device,
+        profile_key,
+    ):
         client = self.getClient(profile_key)
-        return self.findByFeatures(client, namespaces, identities, bare_jids, service, roster, own_jid, local_device)
+        return self.findByFeatures(
+            client,
+            namespaces,
+            identities,
+            bare_jids,
+            service,
+            roster,
+            own_jid,
+            local_device,
+        )
 
     @defer.inlineCallbacks
-    def findByFeatures(self, client, namespaces, identities=None, bare_jids=False, service=True, roster=True, own_jid=True, local_device=False):
+    def findByFeatures(
+        self,
+        client,
+        namespaces,
+        identities=None,
+        bare_jids=False,
+        service=True,
+        roster=True,
+        own_jid=True,
+        local_device=False,
+    ):
         """retrieve all services or contacts managing a set a features
 
         @param namespaces(list[unicode]): features which must be handled
@@ -697,7 +818,9 @@
         if not identities:
             identities = None
         if not namespaces and not identities:
-            raise exceptions.DataError("at least one namespace or one identity must be set")
+            raise exceptions.DataError(
+                "at least one namespace or one identity must be set"
+            )
         found_service = {}
         found_own = {}
         found_roster = {}
@@ -705,9 +828,14 @@
             services_jids = yield self.findFeaturesSet(client, namespaces)
             for service_jid in services_jids:
                 infos = yield self.getDiscoInfos(client, service_jid)
-                if identities is not None and not set(infos.identities.keys()).issuperset(identities):
+                if identities is not None and not set(infos.identities.keys()).issuperset(
+                    identities
+                ):
                     continue
-                found_identities = [(cat, type_, name or u'') for (cat, type_), name in infos.identities.iteritems()]
+                found_identities = [
+                    (cat, type_, name or u"")
+                    for (cat, type_), name in infos.identities.iteritems()
+                ]
                 found_service[service_jid.full()] = found_identities
 
         jids = []
@@ -716,8 +844,10 @@
         if own_jid:
             jids.append(client.jid.userhostJID())
 
-        for found, jids in ((found_own, [client.jid.userhostJID()]),
-                            (found_roster, client.roster.getJids())):
+        for found, jids in (
+            (found_own, [client.jid.userhostJID()]),
+            (found_roster, client.roster.getJids()),
+        ):
             for jid_ in jids:
                 if jid_.resource:
                     if bare_jids:
@@ -737,9 +867,14 @@
                         continue
                     infos = yield self.getDiscoInfos(client, full_jid)
                     if infos.features.issuperset(namespaces):
-                        if identities is not None and not set(infos.identities.keys()).issuperset(identities):
+                        if identities is not None and not set(
+                            infos.identities.keys()
+                        ).issuperset(identities):
                             continue
-                        found_identities = [(cat, type_, name or u'') for (cat, type_), name in infos.identities.iteritems()]
+                        found_identities = [
+                            (cat, type_, name or u"")
+                            for (cat, type_), name in infos.identities.iteritems()
+                        ]
                         found[full_jid.full()] = found_identities
 
         defer.returnValue((found_service, found_own, found_roster))
@@ -750,7 +885,13 @@
         log.debug(u"Killing action {} for timeout".format(keep_id))
         client.actions[keep_id]
 
-    def actionNew(self, action_data, security_limit=C.NO_SECURITY_LIMIT, keep_id=None, profile=C.PROF_KEY_NONE):
+    def actionNew(
+        self,
+        action_data,
+        security_limit=C.NO_SECURITY_LIMIT,
+        keep_id=None,
+        profile=C.PROF_KEY_NONE,
+    ):
         """Shortcut to bridge.actionNew which generate and id and keep for retrieval
 
         @param action_data(dict): action data (see bridge documentation)
@@ -763,7 +904,7 @@
         id_ = unicode(uuid.uuid4())
         if keep_id is not None:
             client = self.getClient(profile)
-            action_timer = reactor.callLater(60*30, self._killAction, keep_id, client)
+            action_timer = reactor.callLater(60 * 30, self._killAction, keep_id, client)
             client.actions[keep_id] = (action_data, id_, security_limit, action_timer)
 
         self.bridge.actionNew(action_data, id_, security_limit, profile)
@@ -776,7 +917,9 @@
         client = self.getClient(profile)
         return [action_tuple[:-1] for action_tuple in client.actions.itervalues()]
 
-    def registerProgressCb(self, progress_id, callback, metadata=None, profile=C.PROF_KEY_NONE):
+    def registerProgressCb(
+        self, progress_id, callback, metadata=None, profile=C.PROF_KEY_NONE
+    ):
         """Register a callback called when progress is requested for id"""
         if metadata is None:
             metadata = {}
@@ -795,7 +938,7 @@
 
     def _progressGet(self, progress_id, profile):
         data = self.progressGet(progress_id, profile)
-        return {k: unicode(v) for k,v in data.iteritems()}
+        return {k: unicode(v) for k, v in data.iteritems()}
 
     def progressGet(self, progress_id, profile):
         """Return a dict with progress information
@@ -837,7 +980,10 @@
             profile = client.profile
             progress_dict = {}
             progress_all[profile] = progress_dict
-            for progress_id, (dummy, progress_metadata) in client._progress_cb.iteritems():
+            for (
+                progress_id,
+                (dummy, progress_metadata),
+            ) in client._progress_cb.iteritems():
                 progress_dict[progress_id] = progress_metadata
         return progress_all
 
@@ -870,7 +1016,7 @@
             one_shot(bool): True to delete callback once it have been called
         @return: id of the registered callback
         """
-        callback_id = kwargs.pop('force_id', None)
+        callback_id = kwargs.pop("force_id", None)
         if callback_id is None:
             callback_id = str(uuid.uuid4())
         else:
@@ -878,12 +1024,14 @@
                 raise exceptions.ConflictError(_(u"id already registered"))
         self._cb_map[callback_id] = (callback, args, kwargs)
 
-        if "one_shot" in kwargs: # One Shot callback are removed after 30 min
+        if "one_shot" in kwargs:  # One Shot callback are removed after 30 min
+
             def purgeCallback():
                 try:
                     self.removeCallback(callback_id)
                 except KeyError:
                     pass
+
             reactor.callLater(1800, purgeCallback)
 
         return callback_id
@@ -906,14 +1054,16 @@
                 - C.BOOL_TRUE
                 - C.BOOL_FALSE
         """
-        # FIXME: security limit need to be checked here
+        #  FIXME: security limit need to be checked here
         try:
             client = self.getClient(profile_key)
         except exceptions.NotFound:
             # client is not available yet
             profile = self.memory.getProfileName(profile_key)
             if not profile:
-                raise exceptions.ProfileUnknownError(_(u'trying to launch action with a non-existant profile'))
+                raise exceptions.ProfileUnknownError(
+                    _(u"trying to launch action with a non-existant profile")
+                )
         else:
             profile = client.profile
             # we check if the action is kept, and remove it
@@ -922,7 +1072,7 @@
             except KeyError:
                 pass
             else:
-                action_tuple[-1].cancel() # the last item is the action timer
+                action_tuple[-1].cancel()  # the last item is the action timer
                 del client.actions[callback_id]
 
         try:
@@ -933,17 +1083,20 @@
         if kwargs.get("with_data", False):
             if data is None:
                 raise exceptions.DataError("Required data for this callback is missing")
-            args,kwargs=list(args)[:],kwargs.copy() # we don't want to modify the original (kw)args
+            args, kwargs = (
+                list(args)[:],
+                kwargs.copy(),
+            )  # we don't want to modify the original (kw)args
             args.insert(0, data)
             kwargs["profile"] = profile
             del kwargs["with_data"]
 
-        if kwargs.pop('one_shot', False):
+        if kwargs.pop("one_shot", False):
             self.removeCallback(callback_id)
 
         return defer.maybeDeferred(callback, *args, **kwargs)
 
-    #Menus management
+    # Menus management
 
     def _getMenuCanonicalPath(self, path):
         """give canonical form of path
@@ -954,7 +1107,14 @@
         """
         return tuple((p.lower().strip() for p in path))
 
-    def importMenu(self, path, callback, security_limit=C.NO_SECURITY_LIMIT, help_string="", type_=C.MENU_GLOBAL):
+    def importMenu(
+        self,
+        path,
+        callback,
+        security_limit=C.NO_SECURITY_LIMIT,
+        help_string="",
+        type_=C.MENU_GLOBAL,
+    ):
         """register a new menu for frontends
 
         @param path(iterable[unicode]): path to go to the menu (category/subcategory/.../item) (e.g.: ("File", "Open"))
@@ -989,34 +1149,40 @@
                 callback, args, kwargs = self._cb_map[callback_id]
             except KeyError:
                 raise exceptions.DataError("Unknown callback id")
-            kwargs["with_data"] = True # we have to be sure that we use extra data
+            kwargs["with_data"] = True  # we have to be sure that we use extra data
         else:
             raise exceptions.DataError("Unknown callback type")
 
         for menu_data in self._menus.itervalues():
-            if menu_data['path'] == path and menu_data['type'] == type_:
-                raise exceptions.ConflictError(_("A menu with the same path and type already exists"))
+            if menu_data["path"] == path and menu_data["type"] == type_:
+                raise exceptions.ConflictError(
+                    _("A menu with the same path and type already exists")
+                )
 
         path_canonical = self._getMenuCanonicalPath(path)
         menu_key = (type_, path_canonical)
 
         if menu_key in self._menus_paths:
-            raise exceptions.ConflictError(u"this menu path is already used: {path} ({menu_key})".format(
-                path=path_canonical, menu_key=menu_key))
+            raise exceptions.ConflictError(
+                u"this menu path is already used: {path} ({menu_key})".format(
+                    path=path_canonical, menu_key=menu_key
+                )
+            )
 
-        menu_data = {'path': tuple(path),
-                     'path_canonical': path_canonical,
-                     'security_limit': security_limit,
-                     'help_string': help_string,
-                     'type': type_
-                    }
+        menu_data = {
+            "path": tuple(path),
+            "path_canonical": path_canonical,
+            "security_limit": security_limit,
+            "help_string": help_string,
+            "type": type_,
+        }
 
         self._menus[callback_id] = menu_data
         self._menus_paths[menu_key] = callback_id
 
         return callback_id
 
-    def getMenus(self, language='', security_limit=C.NO_SECURITY_LIMIT):
+    def getMenus(self, language="", security_limit=C.NO_SECURITY_LIMIT):
         """Return all menus registered
 
         @param language: language used for translation, or empty string for default
@@ -1032,24 +1198,36 @@
         """
         ret = []
         for menu_id, menu_data in self._menus.iteritems():
-            type_ = menu_data['type']
-            path = menu_data['path']
-            menu_security_limit = menu_data['security_limit']
-            if security_limit!=C.NO_SECURITY_LIMIT and (menu_security_limit==C.NO_SECURITY_LIMIT or menu_security_limit>security_limit):
+            type_ = menu_data["type"]
+            path = menu_data["path"]
+            menu_security_limit = menu_data["security_limit"]
+            if security_limit != C.NO_SECURITY_LIMIT and (
+                menu_security_limit == C.NO_SECURITY_LIMIT
+                or menu_security_limit > security_limit
+            ):
                 continue
             languageSwitch(language)
             path_i18n = [_(elt) for elt in path]
             languageSwitch()
-            extra = {} # TODO: manage extra data like icon
+            extra = {}  # TODO: manage extra data like icon
             ret.append((menu_id, type_, path, path_i18n, extra))
 
         return ret
 
-    def _launchMenu(self, menu_type, path, data=None, security_limit=C.NO_SECURITY_LIMIT, profile_key=C.PROF_KEY_NONE):
+    def _launchMenu(
+        self,
+        menu_type,
+        path,
+        data=None,
+        security_limit=C.NO_SECURITY_LIMIT,
+        profile_key=C.PROF_KEY_NONE,
+    ):
         client = self.getClient(profile_key)
         return self.launchMenu(client, menu_type, path, data, security_limit)
 
-    def launchMenu(self, client, menu_type, path, data=None, security_limit=C.NO_SECURITY_LIMIT):
+    def launchMenu(
+        self, client, menu_type, path, data=None, security_limit=C.NO_SECURITY_LIMIT
+    ):
         """launch action a menu action
 
         @param menu_type(unicode): type of menu to launch
@@ -1064,11 +1242,14 @@
         try:
             callback_id = self._menus_paths[menu_key]
         except KeyError:
-            raise exceptions.NotFound(u"Can't find menu {path} ({menu_type})".format(
-                path=canonical_path, menu_type=menu_type))
+            raise exceptions.NotFound(
+                u"Can't find menu {path} ({menu_type})".format(
+                    path=canonical_path, menu_type=menu_type
+                )
+            )
         return self.launchCallback(callback_id, data, client.profile)
 
-    def getMenuHelp(self, menu_id, language=''):
+    def getMenuHelp(self, menu_id, language=""):
         """return the help string of the menu
 
         @param menu_id: id of the menu (same as callback_id)
@@ -1081,6 +1262,6 @@
         except KeyError:
             raise exceptions.DataError("Trying to access an unknown menu")
         languageSwitch(language)
-        help_string = _(menu_data['help_string'])
+        help_string = _(menu_data["help_string"])
         languageSwitch()
         return help_string
--- a/sat/core/xmpp.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/core/xmpp.py	Wed Jun 27 20:14:46 2018 +0200
@@ -31,6 +31,7 @@
 from wokkel import component
 from wokkel import delay
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from sat.core import exceptions
 from zope.interface import implements
@@ -53,10 +54,10 @@
         self.profile = profile
         self.host_app = host_app
         self.cache = cache.Cache(host_app, profile)
-        self._mess_id_uid = {} # map from message id to uid used in history. Key: (full_jid,message_id) Value: uid
+        self._mess_id_uid = {}  # map from message id to uid used in history. Key: (full_jid,message_id) Value: uid
         self.conn_deferred = defer.Deferred()
         self._progress_cb = {}  # callback called when a progress is requested (key = progress id)
-        self.actions = {} # used to keep track of actions for retrieval (key = action_id)
+        self.actions = {}  # used to keep track of actions for retrieval (key = action_id)
 
     ## initialisation ##
 
@@ -117,24 +118,37 @@
         #        but client should not be deleted except if session is finished (independently of connection/deconnection
         #
         try:
-            port = int(host.memory.getParamA(C.FORCE_PORT_PARAM, "Connection", profile_key=profile))
+            port = int(
+                host.memory.getParamA(
+                    C.FORCE_PORT_PARAM, "Connection", profile_key=profile
+                )
+            )
         except ValueError:
             log.debug(_("Can't parse port value, using default value"))
-            port = None  # will use default value 5222 or be retrieved from a DNS SRV record
+            port = (
+                None
+            )  # will use default value 5222 or be retrieved from a DNS SRV record
 
-        password = yield host.memory.asyncGetParamA("Password", "Connection", profile_key=profile)
-        entity = host.profiles[profile] = cls(host, profile,
+        password = yield host.memory.asyncGetParamA(
+            "Password", "Connection", profile_key=profile
+        )
+        entity = host.profiles[profile] = cls(
+            host,
+            profile,
             jid.JID(host.memory.getParamA("JabberID", "Connection", profile_key=profile)),
-            password, host.memory.getParamA(C.FORCE_SERVER_PARAM, "Connection", profile_key=profile) or None,
-            port, max_retries)
+            password,
+            host.memory.getParamA(C.FORCE_SERVER_PARAM, "Connection", profile_key=profile)
+            or None,
+            port,
+            max_retries,
+        )
 
         entity._createSubProtocols()
 
         entity.fallBack = SatFallbackHandler(host)
         entity.fallBack.setHandlerParent(entity)
 
-        entity.versionHandler = SatVersionHandler(C.APP_NAME_FULL,
-                                                  host.full_version)
+        entity.versionHandler = SatVersionHandler(C.APP_NAME_FULL, host.full_version)
         entity.versionHandler.setHandlerParent(entity)
 
         entity.identityHandler = SatIdentityHandler()
@@ -162,10 +176,17 @@
                 log.error(_(u"Plugins initialisation error"))
                 for idx, (success, result) in enumerate(results):
                     if not success:
-                        log.error(u"error (plugin %(name)s): %(failure)s" %
-                                  {'name': plugin_conn_cb[idx][0]._info['import_name'], 'failure': result})
+                        log.error(
+                            u"error (plugin %(name)s): %(failure)s"
+                            % {
+                                "name": plugin_conn_cb[idx][0]._info["import_name"],
+                                "failure": result,
+                            }
+                        )
 
-        yield list_d.addCallback(logPluginResults) # FIXME: we should have a timeout here, and a way to know if a plugin freeze
+        yield list_d.addCallback(
+            logPluginResults
+        )  # FIXME: we should have a timeout here, and a way to know if a plugin freeze
         # TODO: mesure launch time of each plugin
 
     def getConnectionDeferred(self):
@@ -190,9 +211,13 @@
         self._connected.addCallback(self._disconnectionCb)
         self._connected.addErrback(self._disconnectionEb)
 
-        log.info(_(u"********** [{profile}] CONNECTED **********").format(profile=self.profile))
+        log.info(
+            _(u"********** [{profile}] CONNECTED **********").format(profile=self.profile)
+        )
         self.streamInitialized()
-        self.host_app.bridge.connected(self.profile, unicode(self.jid))  # we send the signal to the clients
+        self.host_app.bridge.connected(
+            self.profile, unicode(self.jid)
+        )  # we send the signal to the clients
 
     def _finish_connection(self, dummy):
         self.conn_deferred.callback(None)
@@ -200,7 +225,9 @@
     def streamInitialized(self):
         """Called after _authd"""
         log.debug(_(u"XML stream is initialized"))
-        self.keep_alife = task.LoopingCall(self.xmlstream.send, " ")  # Needed to avoid disconnection (specially with openfire)
+        self.keep_alife = task.LoopingCall(
+            self.xmlstream.send, " "
+        )  # Needed to avoid disconnection (specially with openfire)
         self.keep_alife.start(C.XMPP_KEEP_ALIFE)
 
         self.disco = SatDiscoProtocol(self)
@@ -215,7 +242,12 @@
         disco_d.addCallback(self._finish_connection)
 
     def initializationFailed(self, reason):
-        log.error(_(u"ERROR: XMPP connection failed for profile '%(profile)s': %(reason)s" % {'profile': self.profile, 'reason': reason}))
+        log.error(
+            _(
+                u"ERROR: XMPP connection failed for profile '%(profile)s': %(reason)s"
+                % {"profile": self.profile, "reason": reason}
+            )
+        )
         self.conn_deferred.errback(reason.value)
         try:
             super(SatXMPPEntity, self).initializationFailed(reason)
@@ -231,14 +263,24 @@
         except AttributeError:
             log.debug(_("No keep_alife"))
         if self._connected is not None:
-            self.host_app.bridge.disconnected(self.profile)  # we send the signal to the clients
+            self.host_app.bridge.disconnected(
+                self.profile
+            )  # we send the signal to the clients
             self._connected.callback(None)
-            self.host_app.purgeEntity(self.profile)  # and we remove references to this client
-            log.info(_(u"********** [{profile}] DISCONNECTED **********").format(profile=self.profile))
+            self.host_app.purgeEntity(
+                self.profile
+            )  # and we remove references to this client
+            log.info(
+                _(u"********** [{profile}] DISCONNECTED **********").format(
+                    profile=self.profile
+                )
+            )
         if not self.conn_deferred.called:
             # FIXME: real error is not gotten here (e.g. if jid is not know by Prosody,
             #        we should have the real error)
-            self.conn_deferred.errback(error.StreamError(u"Server unexpectedly closed the connection"))
+            self.conn_deferred.errback(
+                error.StreamError(u"Server unexpectedly closed the connection")
+            )
 
     @defer.inlineCallbacks
     def _cleanConnection(self, dummy):
@@ -265,7 +307,7 @@
 
     ## sending ##
 
-    def IQ(self, type_=u'set', timeout=60):
+    def IQ(self, type_=u"set", timeout=60):
         """shortcut to create an IQ element managing deferred
 
         @param type_(unicode): IQ type ('set' or 'get')
@@ -301,26 +343,28 @@
                 - extra
         @return (dict) message data
         """
-        data['xml'] = message_elt = domish.Element((None, 'message'))
+        data["xml"] = message_elt = domish.Element((None, "message"))
         message_elt["to"] = data["to"].full()
-        message_elt["from"] = data['from'].full()
+        message_elt["from"] = data["from"].full()
         message_elt["type"] = data["type"]
-        if data['uid']: # key must be present but can be set to ''
-                        # by a plugin to avoid id on purpose
-            message_elt['id'] = data['uid']
+        if data["uid"]:  # key must be present but can be set to ''
+            # by a plugin to avoid id on purpose
+            message_elt["id"] = data["uid"]
         for lang, subject in data["subject"].iteritems():
             subject_elt = message_elt.addElement("subject", content=subject)
             if lang:
-                subject_elt[(C.NS_XML, 'lang')] = lang
+                subject_elt[(C.NS_XML, "lang")] = lang
         for lang, message in data["message"].iteritems():
             body_elt = message_elt.addElement("body", content=message)
             if lang:
-                body_elt[(C.NS_XML, 'lang')] = lang
+                body_elt[(C.NS_XML, "lang")] = lang
         try:
-            thread = data['extra']['thread']
+            thread = data["extra"]["thread"]
         except KeyError:
-            if 'thread_parent' in data['extra']:
-                raise exceptions.InternalError(u"thread_parent found while there is not associated thread")
+            if "thread_parent" in data["extra"]:
+                raise exceptions.InternalError(
+                    u"thread_parent found while there is not associated thread"
+                )
         else:
             thread_elt = message_elt.addElement("thread", content=thread)
             try:
@@ -336,7 +380,16 @@
         """
         raise NotImplementedError
 
-    def sendMessage(self, to_jid, message, subject=None, mess_type='auto', extra=None, uid=None, no_trigger=False):
+    def sendMessage(
+        self,
+        to_jid,
+        message,
+        subject=None,
+        mess_type="auto",
+        extra=None,
+        uid=None,
+        no_trigger=False,
+    ):
         """Send a message to an entity
 
         @param to_jid(jid.JID): destinee of the message
@@ -371,18 +424,26 @@
             "extra": extra,
             "timestamp": time.time(),
         }
-        pre_xml_treatments = defer.Deferred() # XXX: plugin can add their pre XML treatments to this deferred
-        post_xml_treatments = defer.Deferred() # XXX: plugin can add their post XML treatments to this deferred
+        pre_xml_treatments = (
+            defer.Deferred()
+        )  # XXX: plugin can add their pre XML treatments to this deferred
+        post_xml_treatments = (
+            defer.Deferred()
+        )  # XXX: plugin can add their post XML treatments to this deferred
 
         if data["type"] == C.MESS_TYPE_AUTO:
             # we try to guess the type
             if data["subject"]:
                 data["type"] = C.MESS_TYPE_NORMAL
-            elif not data["to"].resource:  # if to JID has a resource, the type is not 'groupchat'
+            elif not data[
+                "to"
+            ].resource:  # if to JID has a resource, the type is not 'groupchat'
                 # we may have a groupchat message, we check if the we know this jid
                 try:
-                    entity_type = self.host_app.memory.getEntityData(data["to"], ['type'], self.profile)["type"]
-                    #FIXME: should entity_type manage resources ?
+                    entity_type = self.host_app.memory.getEntityData(
+                        data["to"], ["type"], self.profile
+                    )["type"]
+                    # FIXME: should entity_type manage resources ?
                 except (exceptions.UnknownEntityError, KeyError):
                     entity_type = "contact"
 
@@ -397,19 +458,33 @@
         # FIXME: send_only is used by libervia's OTR plugin to avoid
         #        the triggers from frontend, and no_trigger do the same
         #        thing internally, this could be unified
-        send_only = data['extra'].get('send_only', False)
+        send_only = data["extra"].get("send_only", False)
 
         if not no_trigger and not send_only:
-            if not self.host_app.trigger.point("sendMessage" + self.trigger_suffix, self, data, pre_xml_treatments, post_xml_treatments):
+            if not self.host_app.trigger.point(
+                "sendMessage" + self.trigger_suffix,
+                self,
+                data,
+                pre_xml_treatments,
+                post_xml_treatments,
+            ):
                 return defer.succeed(None)
 
-        log.debug(_(u"Sending message (type {type}, to {to})").format(type=data["type"], to=to_jid.full()))
+        log.debug(
+            _(u"Sending message (type {type}, to {to})").format(
+                type=data["type"], to=to_jid.full()
+            )
+        )
 
         pre_xml_treatments.addCallback(lambda dummy: self.generateMessageXML(data))
         pre_xml_treatments.chainDeferred(post_xml_treatments)
         post_xml_treatments.addCallback(self.sendMessageData)
         if send_only:
-            log.debug(_("Triggers, storage and echo have been inhibited by the 'send_only' parameter"))
+            log.debug(
+                _(
+                    "Triggers, storage and echo have been inhibited by the 'send_only' parameter"
+                )
+            )
         else:
             self.addPostXmlCallbacks(post_xml_treatments)
             post_xml_treatments.addErrback(self._cancelErrorTrap)
@@ -430,10 +505,12 @@
         if data[u"type"] != C.MESS_TYPE_GROUPCHAT:
             # we don't add groupchat message to history, as we get them back
             # and they will be added then
-            if data[u'message'] or data[u'subject']: # we need a message to store
+            if data[u"message"] or data[u"subject"]:  # we need a message to store
                 self.host_app.memory.addToHistory(self, data)
             else:
-               log.warning(u"No message found") # empty body should be managed by plugins before this point
+                log.warning(
+                    u"No message found"
+                )  # empty body should be managed by plugins before this point
         return data
 
     def messageSendToBridge(self, data):
@@ -445,11 +522,23 @@
         if data[u"type"] != C.MESS_TYPE_GROUPCHAT:
             # we don't send groupchat message to bridge, as we get them back
             # and they will be added the
-            if data[u'message'] or data[u'subject']: # we need a message to send something
+            if (
+                data[u"message"] or data[u"subject"]
+            ):  # we need a message to send something
                 # We send back the message, so all frontends are aware of it
-                self.host_app.bridge.messageNew(data[u'uid'], data[u'timestamp'], data[u'from'].full(), data[u'to'].full(), data[u'message'], data[u'subject'], data[u'type'], data[u'extra'], profile=self.profile)
+                self.host_app.bridge.messageNew(
+                    data[u"uid"],
+                    data[u"timestamp"],
+                    data[u"from"].full(),
+                    data[u"to"].full(),
+                    data[u"message"],
+                    data[u"subject"],
+                    data[u"type"],
+                    data[u"extra"],
+                    profile=self.profile,
+                )
             else:
-               log.warning(_(u"No message found"))
+                log.warning(_(u"No message found"))
         return data
 
 
@@ -458,7 +547,16 @@
     trigger_suffix = ""
     is_component = False
 
-    def __init__(self, host_app, profile, user_jid, password, host=None, port=C.XMPP_C2S_PORT, max_retries=C.XMPP_MAX_RETRIES):
+    def __init__(
+        self,
+        host_app,
+        profile,
+        user_jid,
+        password,
+        host=None,
+        port=C.XMPP_C2S_PORT,
+        max_retries=C.XMPP_MAX_RETRIES,
+    ):
         # XXX: DNS SRV records are checked when the host is not specified.
         # If no SRV record is found, the host is directly extracted from the JID.
         self.started = time.time()
@@ -482,25 +580,30 @@
             if isinstance(host_data, basestring):
                 host = host_data
             elif isinstance(host_data, dict):
-                if u'host' in host_data:
-                    host = host_data[u'host']
-                if u'port' in host_data:
-                    port = host_data[u'port']
+                if u"host" in host_data:
+                    host = host_data[u"host"]
+                if u"port" in host_data:
+                    port = host_data[u"port"]
             else:
-                log.warning(_(u"invalid data used for host: {data}").format(data=host_data))
+                log.warning(
+                    _(u"invalid data used for host: {data}").format(data=host_data)
+                )
                 host_data = None
             if host_data is not None:
-                log.info(u"using {host}:{port} for host {host_ori} as requested in config".format(
-                    host_ori = user_jid.host,
-                    host = host,
-                    port = port))
+                log.info(
+                    u"using {host}:{port} for host {host_ori} as requested in config".format(
+                        host_ori=user_jid.host, host=host, port=port
+                    )
+                )
 
-        wokkel_client.XMPPClient.__init__(self, user_jid, password, host or None, port or C.XMPP_C2S_PORT)
+        wokkel_client.XMPPClient.__init__(
+            self, user_jid, password, host or None, port or C.XMPP_C2S_PORT
+        )
         SatXMPPEntity.__init__(self, host_app, profile, max_retries)
 
     def _getPluginsList(self):
         for p in self.host_app.plugins.itervalues():
-            if C.PLUG_MODE_CLIENT in p._info[u'modes']:
+            if C.PLUG_MODE_CLIENT in p._info[u"modes"]:
                 yield p
 
     def _createSubProtocols(self):
@@ -531,9 +634,9 @@
         #      (out of band transmission for instance).
         #      e2e should have a priority of 0 here, and out of band transmission
         #      a lower priority
-        # FIXME: trigger not used yet, can be uncommented when e2e full stanza encryption is implemented
-        # if not self.host_app.trigger.point("send", self, obj):
-        #     return
+        #  FIXME: trigger not used yet, can be uncommented when e2e full stanza encryption is implemented
+        #  if not self.host_app.trigger.point("send", self, obj):
+        #      return
         super(SatXMPPClient, self).send(obj)
 
     def sendMessageData(self, mess_data):
@@ -549,7 +652,7 @@
         #      This is intented for e2e encryption which doesn't do full stanza encryption (e.g. OTR)
         #      This trigger point can't cancel the method
         self.host_app.trigger.point("sendMessageData", self, mess_data)
-        self.send(mess_data[u'xml'])
+        self.send(mess_data[u"xml"])
         return mess_data
 
     def feedback(self, to_jid, message):
@@ -560,15 +663,17 @@
         @param to_jid(jid.JID): destinee jid
         @param message(unicode): message to send to frontends
         """
-        self.host_app.bridge.messageNew(uid=unicode(uuid.uuid4()),
-                                        timestamp=time.time(),
-                                        from_jid=self.jid.full(),
-                                        to_jid=to_jid.full(),
-                                        message={u'': message},
-                                        subject={},
-                                        mess_type=C.MESS_TYPE_INFO,
-                                        extra={},
-                                        profile=self.profile)
+        self.host_app.bridge.messageNew(
+            uid=unicode(uuid.uuid4()),
+            timestamp=time.time(),
+            from_jid=self.jid.full(),
+            to_jid=to_jid.full(),
+            message={u"": message},
+            subject={},
+            mess_type=C.MESS_TYPE_INFO,
+            extra={},
+            profile=self.profile,
+        )
 
     def _finish_connection(self, dummy):
         self.roster.requestRoster()
@@ -583,12 +688,26 @@
     An entry point plugin is launched after component is connected.
     Component need to instantiate MessageProtocol itself
     """
+
     implements(iwokkel.IDisco)
-    trigger_suffix = "Component"  # used for to distinguish some trigger points set in SatXMPPEntity
+    trigger_suffix = (
+        "Component"
+    )  # used for to distinguish some trigger points set in SatXMPPEntity
     is_component = True
-    sendHistory = False  # XXX: set to True from entry plugin to keep messages in history for received messages
+    sendHistory = (
+        False
+    )  # XXX: set to True from entry plugin to keep messages in history for received messages
 
-    def __init__(self, host_app, profile, component_jid, password, host=None, port=None, max_retries=C.XMPP_MAX_RETRIES):
+    def __init__(
+        self,
+        host_app,
+        profile,
+        component_jid,
+        password,
+        host=None,
+        port=None,
+        max_retries=C.XMPP_MAX_RETRIES,
+    ):
         self.started = time.time()
         if port is None:
             port = C.XMPP_COMPONENT_PORT
@@ -598,15 +717,18 @@
         try:
             self.entry_plugin = host_app.plugins[entry_point]
         except KeyError:
-            raise exceptions.NotFound(_(u"The requested entry point ({entry_point}) is not available").format(
-                entry_point = entry_point))
+            raise exceptions.NotFound(
+                _(u"The requested entry point ({entry_point}) is not available").format(
+                    entry_point=entry_point
+                )
+            )
 
         self.identities = [disco.DiscoIdentity(u"component", u"generic", C.APP_NAME)]
         # jid is set automatically on bind by Twisted for Client, but not for Component
         self.jid = component_jid
         if host is None:
             try:
-                host = component_jid.host.split(u'.', 1)[1]
+                host = component_jid.host.split(u".", 1)[1]
             except IndexError:
                 raise ValueError(u"Can't guess host from jid, please specify a host")
         # XXX: component.Component expect unicode jid, while Client expect jid.JID.
@@ -628,14 +750,18 @@
         @raise InternalError: one of the plugin is not handling components
         @raise KeyError: one plugin should be present in self.host_app.plugins but it is not
         """
-        if C.PLUG_MODE_COMPONENT not in current._info[u'modes']:
+        if C.PLUG_MODE_COMPONENT not in current._info[u"modes"]:
             if not required:
                 return
             else:
-                log.error(_(u"Plugin {current_name} is needed for {entry_name}, but it doesn't handle component mode").format(
-                    current_name = current._info[u'import_name'],
-                    entry_name = self.entry_plugin._info[u'import_name']
-                    ))
+                log.error(
+                    _(
+                        u"Plugin {current_name} is needed for {entry_name}, but it doesn't handle component mode"
+                    ).format(
+                        current_name=current._info[u"import_name"],
+                        entry_name=self.entry_plugin._info[u"import_name"],
+                    )
+                )
                 raise exceptions.InternalError(_(u"invalid plugin mode"))
 
         for import_name in current._info.get(C.PI_DEPENDENCIES, []):
@@ -651,7 +777,7 @@
                 dep = self.host_app.plugins[import_name]
             except KeyError:
                 continue
-            self._buildDependencies(dep, plugins, required = False)
+            self._buildDependencies(dep, plugins, required=False)
 
         if current not in plugins:
             # current can be required for several plugins and so
@@ -680,7 +806,6 @@
 
 
 class SatMessageProtocol(xmppim.MessageProtocol):
-
     def __init__(self, host):
         xmppim.MessageProtocol.__init__(self)
         self.host = host
@@ -697,52 +822,60 @@
         message = {}
         subject = {}
         extra = {}
-        data = {"from": jid.JID(message_elt['from']),
-                "to": jid.JID(message_elt['to']),
-                "uid": message_elt.getAttribute('uid', unicode(uuid.uuid4())), # XXX: uid is not a standard attribute but may be added by plugins
-                "message": message,
-                "subject": subject,
-                "type": message_elt.getAttribute('type', 'normal'),
-                "extra": extra}
+        data = {
+            "from": jid.JID(message_elt["from"]),
+            "to": jid.JID(message_elt["to"]),
+            "uid": message_elt.getAttribute(
+                "uid", unicode(uuid.uuid4())
+            ),  # XXX: uid is not a standard attribute but may be added by plugins
+            "message": message,
+            "subject": subject,
+            "type": message_elt.getAttribute("type", "normal"),
+            "extra": extra,
+        }
 
         if client is not None:
             try:
-                data['stanza_id'] = message_elt['id']
+                data["stanza_id"] = message_elt["id"]
             except KeyError:
                 pass
             else:
-                client._mess_id_uid[(data['from'], data['stanza_id'])] = data['uid']
+                client._mess_id_uid[(data["from"], data["stanza_id"])] = data["uid"]
 
         # message
-        for e in message_elt.elements(C.NS_CLIENT, 'body'):
-            message[e.getAttribute((C.NS_XML,'lang'),'')] = unicode(e)
+        for e in message_elt.elements(C.NS_CLIENT, "body"):
+            message[e.getAttribute((C.NS_XML, "lang"), "")] = unicode(e)
 
         # subject
-        for e in message_elt.elements(C.NS_CLIENT, 'subject'):
-            subject[e.getAttribute((C.NS_XML, 'lang'),'')] = unicode(e)
+        for e in message_elt.elements(C.NS_CLIENT, "subject"):
+            subject[e.getAttribute((C.NS_XML, "lang"), "")] = unicode(e)
 
         # delay and timestamp
         try:
-            delay_elt = message_elt.elements(delay.NS_DELAY, 'delay').next()
+            delay_elt = message_elt.elements(delay.NS_DELAY, "delay").next()
         except StopIteration:
-            data['timestamp'] = time.time()
+            data["timestamp"] = time.time()
         else:
             parsed_delay = delay.Delay.fromElement(delay_elt)
-            data['timestamp'] = calendar.timegm(parsed_delay.stamp.utctimetuple())
-            data['received_timestamp'] = unicode(time.time())
+            data["timestamp"] = calendar.timegm(parsed_delay.stamp.utctimetuple())
+            data["received_timestamp"] = unicode(time.time())
             if parsed_delay.sender:
-                data['delay_sender'] = parsed_delay.sender.full()
+                data["delay_sender"] = parsed_delay.sender.full()
         return data
 
     def onMessage(self, message_elt):
         # TODO: handle threads
         client = self.parent
-        if not 'from' in message_elt.attributes:
-            message_elt['from'] = client.jid.host
-        log.debug(_(u"got message from: {from_}").format(from_=message_elt['from']))
-        post_treat = defer.Deferred() # XXX: plugin can add their treatments to this deferred
+        if not "from" in message_elt.attributes:
+            message_elt["from"] = client.jid.host
+        log.debug(_(u"got message from: {from_}").format(from_=message_elt["from"]))
+        post_treat = (
+            defer.Deferred()
+        )  # XXX: plugin can add their treatments to this deferred
 
-        if not self.host.trigger.point("MessageReceived", client, message_elt, post_treat):
+        if not self.host.trigger.point(
+            "MessageReceived", client, message_elt, post_treat
+        ):
             return
 
         data = self.parseMessage(message_elt, client)
@@ -754,25 +887,35 @@
         post_treat.callback(data)
 
     def skipEmptyMessage(self, data):
-        if not data['message'] and not data['extra'] and not data['subject']:
+        if not data["message"] and not data["extra"] and not data["subject"]:
             raise failure.Failure(exceptions.CancelError("Cancelled empty message"))
         return data
 
     def addToHistory(self, data, client):
-        if data.pop(u'history', None) == C.HISTORY_SKIP:
-            log.info(u'history is skipped as requested')
-            data[u'extra'][u'history'] = C.HISTORY_SKIP
+        if data.pop(u"history", None) == C.HISTORY_SKIP:
+            log.info(u"history is skipped as requested")
+            data[u"extra"][u"history"] = C.HISTORY_SKIP
         else:
             return self.host.memory.addToHistory(client, data)
 
     def bridgeSignal(self, dummy, client, data):
         try:
-            data['extra']['received_timestamp'] = data['received_timestamp']
-            data['extra']['delay_sender'] = data['delay_sender']
+            data["extra"]["received_timestamp"] = data["received_timestamp"]
+            data["extra"]["delay_sender"] = data["delay_sender"]
         except KeyError:
             pass
         if data is not None:
-            self.host.bridge.messageNew(data['uid'], data['timestamp'], data['from'].full(), data['to'].full(), data['message'], data['subject'], data['type'], data['extra'], profile=client.profile)
+            self.host.bridge.messageNew(
+                data["uid"],
+                data["timestamp"],
+                data["from"].full(),
+                data["to"].full(),
+                data["message"],
+                data["subject"],
+                data["type"],
+                data["extra"],
+                profile=client.profile,
+            )
         return data
 
     def cancelErrorTrap(self, failure_):
@@ -781,26 +924,29 @@
 
 
 class SatRosterProtocol(xmppim.RosterClientProtocol):
-
     def __init__(self, host):
         xmppim.RosterClientProtocol.__init__(self)
         self.host = host
-        self.got_roster = defer.Deferred() # called when roster is received and ready
-        #XXX: the two following dicts keep a local copy of the roster
+        self.got_roster = defer.Deferred()  # called when roster is received and ready
+        # XXX: the two following dicts keep a local copy of the roster
         self._groups = {}  # map from groups to jids: key=group value=set of jids
         self._jids = None  # map from jids to RosterItem: key=jid value=RosterItem
 
     def rosterCb(self, roster):
-        assert roster is not None # FIXME: must be managed with roster versioning
+        assert roster is not None  # FIXME: must be managed with roster versioning
         self._groups.clear()
         self._jids = roster
         for item in roster.itervalues():
             if not item.subscriptionTo and not item.subscriptionFrom and not item.ask:
-                #XXX: current behaviour: we don't want contact in our roster list
+                # XXX: current behaviour: we don't want contact in our roster list
                 # if there is no presence subscription
                 # may change in the future
-                log.info(u"Removing contact {} from roster because there is no presence subscription".format(item.jid))
-                self.removeItem(item.entity) # FIXME: to be checked
+                log.info(
+                    u"Removing contact {} from roster because there is no presence subscription".format(
+                        item.jid
+                    )
+                )
+                self.removeItem(item.entity)  # FIXME: to be checked
             else:
                 self._registerItem(item)
 
@@ -812,10 +958,16 @@
         """
         log.debug(u"registering item: {}".format(item.entity.full()))
         if item.entity.resource:
-            log.warning(u"Received a roster item with a resource, this is not common but not restricted by RFC 6121, this case may be not well tested.")
+            log.warning(
+                u"Received a roster item with a resource, this is not common but not restricted by RFC 6121, this case may be not well tested."
+            )
         if not item.subscriptionTo:
             if not item.subscriptionFrom:
-                log.info(_(u"There's no subscription between you and [{}]!").format(item.entity.full()))
+                log.info(
+                    _(u"There's no subscription between you and [{}]!").format(
+                        item.entity.full()
+                    )
+                )
             else:
                 log.info(_(u"You are not subscribed to [{}]!").format(item.entity.full()))
         if not item.subscriptionFrom:
@@ -843,16 +995,17 @@
         @param item: RosterItem
         @return: dictionary of attributes
         """
-        item_attr = {'to': unicode(item.subscriptionTo),
-                     'from': unicode(item.subscriptionFrom),
-                     'ask': unicode(item.ask)
-                     }
+        item_attr = {
+            "to": unicode(item.subscriptionTo),
+            "from": unicode(item.subscriptionFrom),
+            "ask": unicode(item.ask),
+        }
         if item.name:
-            item_attr['name'] = item.name
+            item_attr["name"] = item.name
         return item_attr
 
     def setReceived(self, request):
-        #TODO: implement roster versioning (cf RFC 6121 §2.6)
+        # TODO: implement roster versioning (cf RFC 6121 §2.6)
         item = request.item
         try:  # update the cache for the groups the contact has been removed from
             left_groups = set(self._jids[item.entity].groups).difference(item.groups)
@@ -865,7 +1018,9 @@
             pass  # no previous item registration (or it's been cleared)
         self._jids[item.entity] = item
         self._registerItem(item)
-        self.host.bridge.newContact(item.entity.full(), self.getAttributes(item), item.groups, self.parent.profile)
+        self.host.bridge.newContact(
+            item.entity.full(), self.getAttributes(item), item.groups, self.parent.profile
+        )
 
     def removeReceived(self, request):
         entity = request.item.entity
@@ -875,7 +1030,11 @@
         try:
             item = self._jids.pop(entity)
         except KeyError:
-            log.error(u"Received a roster remove event for an item not in cache ({})".format(entity))
+            log.error(
+                u"Received a roster remove event for an item not in cache ({})".format(
+                    entity
+                )
+            )
             return
         for group in item.groups:
             try:
@@ -884,8 +1043,10 @@
                 if not jids_set:
                     del self._groups[group]
             except KeyError:
-                log.warning(u"there is no cache for the group [%(group)s] of the removed roster item [%(jid)s]" %
-                            {"group": group, "jid": entity})
+                log.warning(
+                    u"there is no cache for the group [%(group)s] of the removed roster item [%(jid)s]"
+                    % {"group": group, "jid": entity}
+                )
 
         # then we send the bridge signal
         self.host.bridge.contactDeleted(entity.full(), self.parent.profile)
@@ -939,7 +1100,7 @@
         @return (set(jid.JID)): set of selected jids
         """
         if type_ == C.ALL and groups is not None:
-            raise ValueError('groups must not be set for {} type'.format(C.ALL))
+            raise ValueError("groups must not be set for {} type".format(C.ALL))
 
         if type_ == C.ALL:
             return set(self.getJids())
@@ -949,7 +1110,7 @@
                 jids.update(self.getJidsFromGroup(group))
             return jids
         else:
-            raise ValueError(u'Unexpected type_ {}'.format(type_))
+            raise ValueError(u"Unexpected type_ {}".format(type_))
 
     def getNick(self, entity_jid):
         """Return a nick name for an entity
@@ -965,7 +1126,6 @@
 
 
 class SatPresenceProtocol(xmppim.PresenceClientProtocol):
-
     def __init__(self, host):
         xmppim.PresenceClientProtocol.__init__(self)
         self.host = host
@@ -977,7 +1137,17 @@
         presence_d.addCallback(lambda __: super(SatPresenceProtocol, self).send(obj))
 
     def availableReceived(self, entity, show=None, statuses=None, priority=0):
-        log.debug(_(u"presence update for [%(entity)s] (available, show=%(show)s statuses=%(statuses)s priority=%(priority)d)") % {'entity': entity, C.PRESENCE_SHOW: show, C.PRESENCE_STATUSES: statuses, C.PRESENCE_PRIORITY: priority})
+        log.debug(
+            _(
+                u"presence update for [%(entity)s] (available, show=%(show)s statuses=%(statuses)s priority=%(priority)d)"
+            )
+            % {
+                "entity": entity,
+                C.PRESENCE_SHOW: show,
+                C.PRESENCE_STATUSES: statuses,
+                C.PRESENCE_PRIORITY: priority,
+            }
+        )
 
         if not statuses:
             statuses = {}
@@ -985,20 +1155,25 @@
         if None in statuses:  # we only want string keys
             statuses[C.PRESENCE_STATUSES_DEFAULT] = statuses.pop(None)
 
-        if not self.host.trigger.point("presenceReceived", entity, show, priority, statuses, self.parent.profile):
+        if not self.host.trigger.point(
+            "presenceReceived", entity, show, priority, statuses, self.parent.profile
+        ):
             return
 
-        self.host.memory.setPresenceStatus(entity, show or "",
-                                           int(priority), statuses,
-                                           self.parent.profile)
+        self.host.memory.setPresenceStatus(
+            entity, show or "", int(priority), statuses, self.parent.profile
+        )
 
         # now it's time to notify frontends
-        self.host.bridge.presenceUpdate(entity.full(), show or "",
-                                        int(priority), statuses,
-                                        self.parent.profile)
+        self.host.bridge.presenceUpdate(
+            entity.full(), show or "", int(priority), statuses, self.parent.profile
+        )
 
     def unavailableReceived(self, entity, statuses=None):
-        log.debug(_(u"presence update for [%(entity)s] (unavailable, statuses=%(statuses)s)") % {'entity': entity, C.PRESENCE_STATUSES: statuses})
+        log.debug(
+            _(u"presence update for [%(entity)s] (unavailable, statuses=%(statuses)s)")
+            % {"entity": entity, C.PRESENCE_STATUSES: statuses}
+        )
 
         if not statuses:
             statuses = {}
@@ -1006,21 +1181,33 @@
         if None in statuses:  # we only want string keys
             statuses[C.PRESENCE_STATUSES_DEFAULT] = statuses.pop(None)
 
-        if not self.host.trigger.point("presenceReceived", entity, "unavailable", 0, statuses, self.parent.profile):
+        if not self.host.trigger.point(
+            "presenceReceived", entity, "unavailable", 0, statuses, self.parent.profile
+        ):
             return
 
         # now it's time to notify frontends
         # if the entity is not known yet in this session or is already unavailable, there is no need to send an unavailable signal
         try:
-            presence = self.host.memory.getEntityDatum(entity, "presence", self.parent.profile)
+            presence = self.host.memory.getEntityDatum(
+                entity, "presence", self.parent.profile
+            )
         except (KeyError, exceptions.UnknownEntityError):
             # the entity has not been seen yet in this session
             pass
         else:
             if presence.show != C.PRESENCE_UNAVAILABLE:
-                self.host.bridge.presenceUpdate(entity.full(), C.PRESENCE_UNAVAILABLE, 0, statuses, self.parent.profile)
+                self.host.bridge.presenceUpdate(
+                    entity.full(),
+                    C.PRESENCE_UNAVAILABLE,
+                    0,
+                    statuses,
+                    self.parent.profile,
+                )
 
-        self.host.memory.setPresenceStatus(entity, C.PRESENCE_UNAVAILABLE, 0, statuses, self.parent.profile)
+        self.host.memory.setPresenceStatus(
+            entity, C.PRESENCE_UNAVAILABLE, 0, statuses, self.parent.profile
+        )
 
     def available(self, entity=None, show=None, statuses=None, priority=None):
         """Set a presence and statuses.
@@ -1032,7 +1219,11 @@
         """
         if priority is None:
             try:
-                priority = int(self.host.memory.getParamA("Priority", "Connection", profile_key=self.parent.profile))
+                priority = int(
+                    self.host.memory.getParamA(
+                        "Priority", "Connection", profile_key=self.parent.profile
+                    )
+                )
             except ValueError:
                 priority = 0
 
@@ -1048,7 +1239,7 @@
 
         # ... before switching back
         if None in statuses:
-            statuses['default'] = statuses.pop(None)
+            statuses["default"] = statuses.pop(None)
 
         if not self.host.trigger.point("presence_available", presence_elt, self.parent):
             return
@@ -1060,7 +1251,9 @@
         xmppim.PresenceClientProtocol.subscribed(self, entity)
         self.host.memory.delWaitingSub(entity.userhost(), self.parent.profile)
         item = self.parent.roster.getItem(entity)
-        if not item or not item.subscriptionTo:  # we automatically subscribe to 'to' presence
+        if (
+            not item or not item.subscriptionTo
+        ):  # we automatically subscribe to 'to' presence
             log.debug(_('sending automatic "from" subscription request'))
             self.subscribe(entity)
 
@@ -1070,11 +1263,11 @@
 
     def subscribedReceived(self, entity):
         log.debug(_(u"subscription approved for [%s]") % entity.userhost())
-        self.host.bridge.subscribe('subscribed', entity.userhost(), self.parent.profile)
+        self.host.bridge.subscribe("subscribed", entity.userhost(), self.parent.profile)
 
     def unsubscribedReceived(self, entity):
         log.debug(_(u"unsubscription confirmed for [%s]") % entity.userhost())
-        self.host.bridge.subscribe('unsubscribed', entity.userhost(), self.parent.profile)
+        self.host.bridge.subscribe("unsubscribed", entity.userhost(), self.parent.profile)
 
     @defer.inlineCallbacks
     def subscribeReceived(self, entity):
@@ -1083,11 +1276,15 @@
         item = self.parent.roster.getItem(entity)
         if item and item.subscriptionTo:
             # We automatically accept subscription if we are already subscribed to contact presence
-            log.debug(_('sending automatic subscription acceptance'))
+            log.debug(_("sending automatic subscription acceptance"))
             self.subscribed(entity)
         else:
-            self.host.memory.addWaitingSub('subscribe', entity.userhost(), self.parent.profile)
-            self.host.bridge.subscribe('subscribe', entity.userhost(), self.parent.profile)
+            self.host.memory.addWaitingSub(
+                "subscribe", entity.userhost(), self.parent.profile
+            )
+            self.host.bridge.subscribe(
+                "subscribe", entity.userhost(), self.parent.profile
+            )
 
     @defer.inlineCallbacks
     def unsubscribeReceived(self, entity):
@@ -1095,9 +1292,9 @@
         yield self.parent.roster.got_roster
         item = self.parent.roster.getItem(entity)
         if item and item.subscriptionFrom:  # we automatically remove contact
-            log.debug(_('automatic contact deletion'))
+            log.debug(_("automatic contact deletion"))
             self.host.delContact(entity, self.parent.profile)
-        self.host.bridge.subscribe('unsubscribe', entity.userhost(), self.parent.profile)
+        self.host.bridge.subscribe("unsubscribe", entity.userhost(), self.parent.profile)
 
 
 class SatDiscoProtocol(disco.DiscoClientProtocol):
@@ -1117,9 +1314,8 @@
 
 
 class SatVersionHandler(generic.VersionHandler):
-
     def getDiscoInfo(self, requestor, target, node):
-        #XXX: We need to work around wokkel's behaviour (namespace not added if there is a
+        # XXX: We need to work around wokkel's behaviour (namespace not added if there is a
         # node) as it cause issues with XEP-0115 & PEP (XEP-0163): there is a node when server
         # ask for disco info, and not when we generate the key, so the hash is used with different
         # disco features, and when the server (seen on ejabberd) generate its own hash for security check
@@ -1131,11 +1327,12 @@
     """ Manage disco Identity of SàT.
 
     """
-    #TODO: dynamic identity update (see docstring). Note that a XMPP entity can have several identities
+
+    # TODO: dynamic identity update (see docstring). Note that a XMPP entity can have several identities
     implements(iwokkel.IDisco)
 
-    def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
+    def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
         return self.parent.identities
 
-    def getDiscoItems(self, requestor, target, nodeIdentifier=''):
+    def getDiscoItems(self, requestor, target, nodeIdentifier=""):
         return []
--- a/sat/memory/cache.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/memory/cache.py	Wed Jun 27 20:14:46 2018 +0200
@@ -18,6 +18,7 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from sat.tools.common import regex
 from sat.core import exceptions
@@ -27,7 +28,7 @@
 import os.path
 import time
 
-DEFAULT_EXT = '.raw'
+DEFAULT_EXT = ".raw"
 
 
 class Cache(object):
@@ -39,11 +40,11 @@
             if None, the cache will be common for all profiles
         """
         self.profile = profile
-        path_elts = [host.memory.getConfig('', 'local_dir'), C.CACHE_DIR]
+        path_elts = [host.memory.getConfig("", "local_dir"), C.CACHE_DIR]
         if profile:
-            path_elts.extend([u'profiles',regex.pathEscape(profile)])
+            path_elts.extend([u"profiles", regex.pathEscape(profile)])
         else:
-            path_elts.append(u'common')
+            path_elts.append(u"common")
         self.cache_dir = os.path.join(*path_elts)
 
         if not os.path.exists(self.cache_dir):
@@ -54,8 +55,10 @@
 
         @param filename(unicode): cached file name (cache data or actual file)
         """
-        if not filename or u'/' in filename:
-            log.error(u"invalid char found in file name, hack attempt? name:{}".format(filename))
+        if not filename or u"/" in filename:
+            log.error(
+                u"invalid char found in file name, hack attempt? name:{}".format(filename)
+            )
             raise exceptions.DataError(u"Invalid char found")
         return os.path.join(self.cache_dir, filename)
 
@@ -76,26 +79,27 @@
             return None
 
         try:
-            with open(cache_url, 'rb') as f:
+            with open(cache_url, "rb") as f:
                 cache_data = pickle.load(f)
         except IOError:
             log.warning(u"can't read cache at {}".format(cache_url))
             return None
         except pickle.UnpicklingError:
-            log.warning(u'invalid cache found at {}'.format(cache_url))
+            log.warning(u"invalid cache found at {}".format(cache_url))
             return None
 
         try:
-            eol = cache_data['eol']
+            eol = cache_data["eol"]
         except KeyError:
-            log.warning(u'no End Of Life found for cached file {}'.format(uid))
+            log.warning(u"no End Of Life found for cached file {}".format(uid))
             eol = 0
         if eol < time.time():
-            log.debug(u"removing expired cache (expired for {}s)".format(
-                time.time() - eol))
+            log.debug(
+                u"removing expired cache (expired for {}s)".format(time.time() - eol)
+            )
             return None
 
-        cache_data['path'] = self.getPath(cache_data['filename'])
+        cache_data["path"] = self.getPath(cache_data["filename"])
         return cache_data
 
     def getFilePath(self, uid):
@@ -107,7 +111,7 @@
         """
         metadata = self.getMetadata(uid)
         if metadata is not None:
-            return metadata['path']
+            return metadata["path"]
 
     def cacheData(self, source, uid, mime_type=None, max_age=None, filename=None):
         """create cache metadata and file object to use for actual data
@@ -130,24 +134,27 @@
             if mime_type:
                 ext = mimetypes.guess_extension(mime_type, strict=False)
                 if ext is None:
-                    log.warning(u"can't find extension for MIME type {}".format(mime_type))
+                    log.warning(
+                        u"can't find extension for MIME type {}".format(mime_type)
+                    )
                     ext = DEFAULT_EXT
-                elif ext == u'.jpe':
-                    ext = u'.jpg'
+                elif ext == u".jpe":
+                    ext = u".jpg"
             else:
                 ext = DEFAULT_EXT
                 mime_type = None
             filename = uid + ext
         if max_age is None:
             max_age = C.DEFAULT_MAX_AGE
-        cache_data = {u'source': source,
-                      u'filename': filename,
-                      u'eol': int(time.time()) + max_age,
-                      u'mime_type': mime_type,
-                      }
+        cache_data = {
+            u"source": source,
+            u"filename": filename,
+            u"eol": int(time.time()) + max_age,
+            u"mime_type": mime_type,
+        }
         file_path = self.getPath(filename)
 
-        with open(cache_url, 'wb') as f:
+        with open(cache_url, "wb") as f:
             pickle.dump(cache_data, f, protocol=2)
 
-        return open(file_path, 'wb')
+        return open(file_path, "wb")
--- a/sat/memory/crypto.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/memory/crypto.py	Wed Jun 27 20:14:46 2018 +0200
@@ -47,13 +47,17 @@
         @param leave_empty (bool): if True, empty text will be returned "as is"
         @return: Deferred: base-64 encoded str
         """
-        if leave_empty and text == '':
+        if leave_empty and text == "":
             return succeed(text)
         iv = BlockCipher.getRandomKey()
-        key = key.encode('utf-8')
-        key = key[:BlockCipher.MAX_KEY_SIZE] if len(key) >= BlockCipher.MAX_KEY_SIZE else BlockCipher.pad(key)
+        key = key.encode("utf-8")
+        key = (
+            key[: BlockCipher.MAX_KEY_SIZE]
+            if len(key) >= BlockCipher.MAX_KEY_SIZE
+            else BlockCipher.pad(key)
+        )
         cipher = AES.new(key, AES.MODE_CFB, iv)
-        d = deferToThread(cipher.encrypt, BlockCipher.pad(text.encode('utf-8')))
+        d = deferToThread(cipher.encrypt, BlockCipher.pad(text.encode("utf-8")))
         d.addCallback(lambda ciphertext: b64encode(iv + ciphertext))
         return d
 
@@ -68,12 +72,19 @@
         @param leave_empty (bool): if True, empty ciphertext will be returned "as is"
         @return: Deferred: str or None if the password could not be decrypted
         """
-        if leave_empty and ciphertext == '':
-            return succeed('')
+        if leave_empty and ciphertext == "":
+            return succeed("")
         ciphertext = b64decode(ciphertext)
-        iv, ciphertext = ciphertext[:BlockCipher.IV_SIZE], ciphertext[BlockCipher.IV_SIZE:]
-        key = key.encode('utf-8')
-        key = key[:BlockCipher.MAX_KEY_SIZE] if len(key) >= BlockCipher.MAX_KEY_SIZE else BlockCipher.pad(key)
+        iv, ciphertext = (
+            ciphertext[: BlockCipher.IV_SIZE],
+            ciphertext[BlockCipher.IV_SIZE :],
+        )
+        key = key.encode("utf-8")
+        key = (
+            key[: BlockCipher.MAX_KEY_SIZE]
+            if len(key) >= BlockCipher.MAX_KEY_SIZE
+            else BlockCipher.pad(key)
+        )
         cipher = AES.new(key, AES.MODE_CFB, iv)
         d = deferToThread(cipher.decrypt, ciphertext)
         d.addCallback(lambda text: BlockCipher.unpad(text))
@@ -81,7 +92,7 @@
         # a decrypted empty value and a decryption failure... both return
         # the empty value. Fortunately, we detect empty passwords beforehand
         # thanks to the "leave_empty" parameter which is used by default.
-        d.addCallback(lambda text: text.decode('utf-8') if text else None)
+        d.addCallback(lambda text: text.decode("utf-8") if text else None)
         return d
 
     @classmethod
@@ -108,7 +119,7 @@
     @classmethod
     def unpad(self, s):
         """Method from http://stackoverflow.com/a/12525165"""
-        return s[0:-ord(s[-1])]
+        return s[0 : -ord(s[-1])]
 
 
 class PasswordHasher(object):
@@ -124,9 +135,13 @@
         @param leave_empty (bool): if True, empty password will be returned "as is"
         @return: Deferred: base-64 encoded str
         """
-        if leave_empty and password == '':
+        if leave_empty and password == "":
             return succeed(password)
-        salt = b64decode(salt)[:PasswordHasher.SALT_LEN] if salt else urandom(PasswordHasher.SALT_LEN)
+        salt = (
+            b64decode(salt)[: PasswordHasher.SALT_LEN]
+            if salt
+            else urandom(PasswordHasher.SALT_LEN)
+        )
         d = deferToThread(PBKDF2, password, salt)
         d.addCallback(lambda hashed: b64encode(salt + hashed))
         return d
@@ -139,7 +154,7 @@
         @param hashed (str): the hash of the password
         @return: Deferred: boolean
         """
-        leave_empty = hashed == ''
+        leave_empty = hashed == ""
         d = PasswordHasher.hash(attempt, hashed, leave_empty)
         d.addCallback(lambda hashed_attempt: hashed_attempt == hashed)
         return d
--- a/sat/memory/disco.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/memory/disco.py	Wed Jun 27 20:14:46 2018 +0200
@@ -20,6 +20,7 @@
 from sat.core.i18n import _
 from sat.core import exceptions
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from twisted.words.protocols.jabber import jid
 from twisted.words.protocols.jabber.error import StanzaError
@@ -35,7 +36,8 @@
 
 
 TIMEOUT = 15
-CAP_HASH_ERROR = 'ERROR'
+CAP_HASH_ERROR = "ERROR"
+
 
 class HashGenerationError(Exception):
     pass
@@ -46,10 +48,10 @@
 
     def __init__(self, identity, lang=None):
         assert isinstance(identity, disco.DiscoIdentity)
-        self.category = identity.category.encode('utf-8')
-        self.idType = identity.type.encode('utf-8')
-        self.name = identity.name.encode('utf-8') if identity.name else ''
-        self.lang = lang.encode('utf-8') if lang is not None else ''
+        self.category = identity.category.encode("utf-8")
+        self.idType = identity.type.encode("utf-8")
+        self.name = identity.name.encode("utf-8") if identity.name else ""
+        self.lang = lang.encode("utf-8") if lang is not None else ""
 
     def __str__(self):
         return "%s/%s/%s/%s" % (self.category, self.idType, self.lang, self.name)
@@ -63,8 +65,8 @@
 
     def __init__(self, persistent):
         self.hashes = {
-            CAP_HASH_ERROR: disco.DiscoInfo(), # used when we can't get disco infos
-            }
+            CAP_HASH_ERROR: disco.DiscoInfo()  # used when we can't get disco infos
+        }
         self.persistent = persistent
 
     def __getitem__(self, key):
@@ -86,12 +88,16 @@
                 element = xml_tools.ElementParser()(xml)
                 disco_info = disco.DiscoInfo.fromElement(element)
                 if not disco_info.features and not disco_info.identities:
-                    log.warning(_(u"no feature/identity found in disco element (hash: {cap_hash}), ignoring: {xml}").format(
-                        cap_hash=hash_, xml=xml))
+                    log.warning(
+                        _(
+                            u"no feature/identity found in disco element (hash: {cap_hash}), ignoring: {xml}"
+                        ).format(cap_hash=hash_, xml=xml)
+                    )
                 else:
                     self.hashes[hash_] = disco_info
 
             log.info(u"Disco hashes loaded")
+
         d = self.persistent.load()
         d.addCallback(fillHashes)
         return d
@@ -110,7 +116,7 @@
         return self.hashes.load()
 
     @defer.inlineCallbacks
-    def hasFeature(self, client, feature, jid_=None, node=u''):
+    def hasFeature(self, client, feature, jid_=None, node=u""):
         """Tell if an entity has the required feature
 
         @param feature: feature namespace
@@ -122,7 +128,7 @@
         defer.returnValue(feature in disco_infos.features)
 
     @defer.inlineCallbacks
-    def checkFeature(self, client, feature, jid_=None, node=u''):
+    def checkFeature(self, client, feature, jid_=None, node=u""):
         """Like hasFeature, but raise an exception is feature is not Found
 
         @param feature: feature namespace
@@ -136,7 +142,7 @@
             raise failure.Failure(exceptions.FeatureNotFound)
 
     @defer.inlineCallbacks
-    def checkFeatures(self, client, features, jid_=None, identity=None, node=u''):
+    def checkFeatures(self, client, features, jid_=None, identity=None, node=u""):
         """Like checkFeature, but check several features at once, and check also identity
 
         @param features(iterable[unicode]): features to check
@@ -153,7 +159,7 @@
         if identity is not None and identity not in disco_infos.identities:
             raise failure.Failure(exceptions.FeatureNotFound())
 
-    def getInfos(self, client, jid_=None, node=u'', use_cache=True):
+    def getInfos(self, client, jid_=None, node=u"", use_cache=True):
         """get disco infos from jid_, filling capability hash if needed
 
         @param jid_: jid of the target, or None for profile's server
@@ -167,14 +173,19 @@
             if not use_cache:
                 # we ignore cache, so we pretend we haven't found it
                 raise KeyError
-            cap_hash = self.host.memory.getEntityData(jid_, [C.ENTITY_CAP_HASH], client.profile)[C.ENTITY_CAP_HASH]
+            cap_hash = self.host.memory.getEntityData(
+                jid_, [C.ENTITY_CAP_HASH], client.profile
+            )[C.ENTITY_CAP_HASH]
         except (KeyError, exceptions.UnknownEntityError):
             # capability hash is not available, we'll compute one
             def infosCb(disco_infos):
                 cap_hash = self.generateHash(disco_infos)
                 self.hashes[cap_hash] = disco_infos
-                self.host.memory.updateEntityData(jid_, C.ENTITY_CAP_HASH, cap_hash, profile_key=client.profile)
+                self.host.memory.updateEntityData(
+                    jid_, C.ENTITY_CAP_HASH, cap_hash, profile_key=client.profile
+                )
                 return disco_infos
+
             def infosEb(fail):
                 if fail.check(defer.CancelledError):
                     reason = u"request time-out"
@@ -183,10 +194,17 @@
                         reason = unicode(fail.value)
                     except AttributeError:
                         reason = unicode(fail)
-                    log.warning(u"Error while requesting disco infos from {jid}: {reason}".format(jid=jid_.full(), reason=reason))
-                self.host.memory.updateEntityData(jid_, C.ENTITY_CAP_HASH, CAP_HASH_ERROR, profile_key=client.profile)
+                    log.warning(
+                        u"Error while requesting disco infos from {jid}: {reason}".format(
+                            jid=jid_.full(), reason=reason
+                        )
+                    )
+                self.host.memory.updateEntityData(
+                    jid_, C.ENTITY_CAP_HASH, CAP_HASH_ERROR, profile_key=client.profile
+                )
                 disco_infos = self.hashes[CAP_HASH_ERROR]
                 return disco_infos
+
             d = client.disco.requestInfo(jid_, nodeIdentifier=node)
             d.addCallback(infosCb)
             d.addErrback(infosEb)
@@ -196,7 +214,7 @@
             return defer.succeed(disco_infos)
 
     @defer.inlineCallbacks
-    def getItems(self, client, jid_=None, node=u'', use_cache=True):
+    def getItems(self, client, jid_=None, node=u"", use_cache=True):
         """get disco items from jid_, cache them for our own server
 
         @param jid_(jid.JID): jid of the target, or None for profile's server
@@ -211,7 +229,9 @@
         if jid_ == server_jid and not node:
             # we cache items only for our own server and if node is not set
             try:
-                items = self.host.memory.getEntityData(jid_, ["DISCO_ITEMS"], client.profile)["DISCO_ITEMS"]
+                items = self.host.memory.getEntityData(
+                    jid_, ["DISCO_ITEMS"], client.profile
+                )["DISCO_ITEMS"]
                 log.debug(u"[%s] disco items are in cache" % jid_.full())
                 if not use_cache:
                     # we ignore cache, so we pretend we haven't found it
@@ -219,22 +239,28 @@
             except (KeyError, exceptions.UnknownEntityError):
                 log.debug(u"Caching [%s] disco items" % jid_.full())
                 items = yield client.disco.requestItems(jid_, nodeIdentifier=node)
-                self.host.memory.updateEntityData(jid_, "DISCO_ITEMS", items, profile_key=client.profile)
+                self.host.memory.updateEntityData(
+                    jid_, "DISCO_ITEMS", items, profile_key=client.profile
+                )
         else:
             try:
                 items = yield client.disco.requestItems(jid_, nodeIdentifier=node)
             except StanzaError as e:
-                log.warning(u"Error while requesting items for {jid}: {reason}"
-                    .format(jid=jid_.full(), reason=e.condition))
+                log.warning(
+                    u"Error while requesting items for {jid}: {reason}".format(
+                        jid=jid_.full(), reason=e.condition
+                    )
+                )
                 items = disco.DiscoItems()
 
         defer.returnValue(items)
 
-
     def _infosEb(self, failure_, entity_jid):
         failure_.trap(StanzaError)
-        log.warning(_(u"Error while requesting [%(jid)s]: %(error)s") % {'jid': entity_jid.full(),
-                                                                    'error': failure_.getErrorMessage()})
+        log.warning(
+            _(u"Error while requesting [%(jid)s]: %(error)s")
+            % {"jid": entity_jid.full(), "error": failure_.getErrorMessage()}
+        )
 
     def findServiceEntity(self, client, category, type_, jid_=None):
         """Helper method to find first available entity from findServiceEntities
@@ -265,14 +291,18 @@
             defers_list = []
             for item in items:
                 info_d = self.getInfos(client, item.entity)
-                info_d.addCallbacks(infosCb, self._infosEb, [item.entity], None, [item.entity])
+                info_d.addCallbacks(
+                    infosCb, self._infosEb, [item.entity], None, [item.entity]
+                )
                 defers_list.append(info_d)
             return defer.DeferredList(defers_list)
 
         d = self.getItems(client, jid_)
         d.addCallback(gotItems)
         d.addCallback(lambda dummy: found_entities)
-        reactor.callLater(TIMEOUT, d.cancel) # FIXME: one bad service make a general timeout
+        reactor.callLater(
+            TIMEOUT, d.cancel
+        )  # FIXME: one bad service make a general timeout
         return d
 
     def findFeaturesSet(self, client, features, identity=None, jid_=None):
@@ -291,7 +321,7 @@
 
         def infosCb(infos, entity):
             if entity is None:
-                log.warning(_(u'received an item without jid'))
+                log.warning(_(u"received an item without jid"))
                 return
             if identity is not None and identity not in infos.identities:
                 return
@@ -309,7 +339,9 @@
         d = self.getItems(client, jid_)
         d.addCallback(gotItems)
         d.addCallback(lambda dummy: found_entities)
-        reactor.callLater(TIMEOUT, d.cancel) # FIXME: one bad service make a general timeout
+        reactor.callLater(
+            TIMEOUT, d.cancel
+        )  # FIXME: one bad service make a general timeout
         return d
 
     def generateHash(self, services):
@@ -320,25 +352,35 @@
 
         """
         s = []
-        byte_identities = [ByteIdentity(service) for service in services if isinstance(service, disco.DiscoIdentity)]  # FIXME: lang must be managed here
+        byte_identities = [
+            ByteIdentity(service)
+            for service in services
+            if isinstance(service, disco.DiscoIdentity)
+        ]  # FIXME: lang must be managed here
         byte_identities.sort(key=lambda i: i.lang)
         byte_identities.sort(key=lambda i: i.idType)
         byte_identities.sort(key=lambda i: i.category)
         for identity in byte_identities:
             s.append(str(identity))
-            s.append('<')
-        byte_features = [service.encode('utf-8') for service in services if isinstance(service, disco.DiscoFeature)]
+            s.append("<")
+        byte_features = [
+            service.encode("utf-8")
+            for service in services
+            if isinstance(service, disco.DiscoFeature)
+        ]
         byte_features.sort()  # XXX: the default sort has the same behaviour as the requested RFC 4790 i;octet sort
         for feature in byte_features:
             s.append(feature)
-            s.append('<')
-        #TODO: manage XEP-0128 data form here
-        cap_hash = b64encode(sha1(''.join(s)).digest())
-        log.debug(_(u'Capability hash generated: [%s]') % cap_hash)
+            s.append("<")
+        # TODO: manage XEP-0128 data form here
+        cap_hash = b64encode(sha1("".join(s)).digest())
+        log.debug(_(u"Capability hash generated: [%s]") % cap_hash)
         return cap_hash
 
     @defer.inlineCallbacks
-    def _discoInfos(self, entity_jid_s, node=u'', use_cache=True, profile_key=C.PROF_KEY_NONE):
+    def _discoInfos(
+        self, entity_jid_s, node=u"", use_cache=True, profile_key=C.PROF_KEY_NONE
+    ):
         """ Discovery method for the bridge
         @param entity_jid_s: entity we want to discover
         @param use_cache(bool): if True, use cached data if available
@@ -353,8 +395,8 @@
         for form_type, form in disco_infos.extensions.items():
             fields = []
             for field in form.fieldList:
-                data = {'type': field.fieldType}
-                for attr in ('var', 'label', 'desc'):
+                data = {"type": field.fieldType}
+                for attr in ("var", "label", "desc"):
                     value = getattr(field, attr)
                     if value is not None:
                         data[attr] = value
@@ -364,9 +406,16 @@
 
             extensions[form_type or ""] = fields
 
-        defer.returnValue((disco_infos.features,
-            [(cat, type_, name or '') for (cat, type_), name in disco_infos.identities.items()],
-            extensions))
+        defer.returnValue(
+            (
+                disco_infos.features,
+                [
+                    (cat, type_, name or "")
+                    for (cat, type_), name in disco_infos.identities.items()
+                ],
+                extensions,
+            )
+        )
 
     def items2tuples(self, disco_items):
         """convert disco items to tuple of strings
@@ -378,10 +427,12 @@
             if not item.entity:
                 log.warning(_(u"invalid item (no jid)"))
                 continue
-            yield (item.entity.full(), item.nodeIdentifier or '', item.name or '')
+            yield (item.entity.full(), item.nodeIdentifier or "", item.name or "")
 
     @defer.inlineCallbacks
-    def _discoItems(self, entity_jid_s, node=u'', use_cache=True, profile_key=C.PROF_KEY_NONE):
+    def _discoItems(
+        self, entity_jid_s, node=u"", use_cache=True, profile_key=C.PROF_KEY_NONE
+    ):
         """ Discovery method for the bridge
 
         @param entity_jid_s: entity we want to discover
--- a/sat/memory/memory.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/memory/memory.py	Wed Jun 27 20:14:46 2018 +0200
@@ -20,6 +20,7 @@
 from sat.core.i18n import _
 
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 
 import os.path
@@ -44,11 +45,13 @@
 import time
 
 
-PresenceTuple = namedtuple("PresenceTuple", ('show', 'priority', 'statuses'))
+PresenceTuple = namedtuple("PresenceTuple", ("show", "priority", "statuses"))
 MSG_NO_SESSION = "Session id doesn't exist or is finished"
 
+
 class Sessions(object):
     """Sessions are data associated to key used for a temporary moment, with optional profile checking."""
+
     DEFAULT_TIMEOUT = 600
 
     def __init__(self, timeout=None, resettable_timeout=True):
@@ -72,11 +75,15 @@
         if session_id is None:
             session_id = str(uuid4())
         elif session_id in self._sessions:
-            raise exceptions.ConflictError(u"Session id {} is already used".format(session_id))
+            raise exceptions.ConflictError(
+                u"Session id {} is already used".format(session_id)
+            )
         timer = reactor.callLater(self.timeout, self._purgeSession, session_id)
         if session_data is None:
             session_data = {}
-        self._sessions[session_id] = (timer, session_data) if profile is None else (timer, session_data, profile)
+        self._sessions[session_id] = (
+            (timer, session_data) if profile is None else (timer, session_data, profile)
+        )
         return session_id, session_data
 
     def _purgeSession(self, session_id):
@@ -91,7 +98,12 @@
             # if the session is time-outed, the timer has been called
             pass
         del self._sessions[session_id]
-        log.debug(u"Session {} purged{}".format(session_id, u' (profile {})'.format(profile) if profile is not None else u''))
+        log.debug(
+            u"Session {} purged{}".format(
+                session_id,
+                u" (profile {})".format(profile) if profile is not None else u"",
+            )
+        )
 
     def __len__(self):
         return len(self._sessions)
@@ -103,7 +115,9 @@
         try:
             timer, session_data, profile_set = self._sessions[session_id]
         except ValueError:
-            raise exceptions.InternalError("You need to use __getitem__ when profile is not set")
+            raise exceptions.InternalError(
+                "You need to use __getitem__ when profile is not set"
+            )
         except KeyError:
             raise failure.Failure(KeyError(MSG_NO_SESSION))
         if profile_set != profile:
@@ -116,7 +130,9 @@
         try:
             timer, session_data = self._sessions[session_id]
         except ValueError:
-            raise exceptions.InternalError("You need to use profileGet instead of __getitem__ when profile is set")
+            raise exceptions.InternalError(
+                "You need to use profileGet instead of __getitem__ when profile is set"
+            )
         except KeyError:
             raise failure.Failure(KeyError(MSG_NO_SESSION))
         if self.resettable_timeout:
@@ -169,8 +185,12 @@
         """
         ids = self._profileGetAllIds(profile)
         if len(ids) > 1:
-            raise exceptions.InternalError('profileGetUnique has been used but more than one session has been found!')
-        return self.profileGet(ids[0], profile) if len(ids) == 1 else None  # XXX: timeout might be reset
+            raise exceptions.InternalError(
+                "profileGetUnique has been used but more than one session has been found!"
+            )
+        return (
+            self.profileGet(ids[0], profile) if len(ids) == 1 else None
+        )  # XXX: timeout might be reset
 
     def profileDelUnique(self, profile):
         """Delete the unique session that is associated to the given profile.
@@ -180,7 +200,9 @@
         """
         ids = self._profileGetAllIds(profile)
         if len(ids) > 1:
-            raise exceptions.InternalError('profileDelUnique has been used but more than one session has been found!')
+            raise exceptions.InternalError(
+                "profileDelUnique has been used but more than one session has been found!"
+            )
         if len(ids) == 1:
             del self._sessions[ids[0]]
 
@@ -194,7 +216,9 @@
         ProfileSessions.__init__(self, timeout, resettable_timeout=False)
 
     def _purgeSession(self, session_id):
-        log.debug("FIXME: PasswordSessions should ask for the profile password after the session expired")
+        log.debug(
+            "FIXME: PasswordSessions should ask for the profile password after the session expired"
+        )
 
 
 # XXX: tmp update code, will be removed in the future
@@ -211,16 +235,20 @@
     except:
         pass  # file is readable but its structure if wrong
     try:
-        current_value = user_config.get('DEFAULT', 'local_dir')
+        current_value = user_config.get("DEFAULT", "local_dir")
     except (NoOptionError, NoSectionError):
-        current_value = ''
+        current_value = ""
     if current_value:
         return  # nothing to do
-    old_default = '~/.sat'
-    if os.path.isfile(os.path.expanduser(old_default) + '/' + C.SAVEFILE_DATABASE):
+    old_default = "~/.sat"
+    if os.path.isfile(os.path.expanduser(old_default) + "/" + C.SAVEFILE_DATABASE):
         if not silent:
-            log.warning(_(u"A database has been found in the default local_dir for previous versions (< 0.5)"))
-        tools_config.fixConfigOption('', 'local_dir', old_default, silent)
+            log.warning(
+                _(
+                    u"A database has been found in the default local_dir for previous versions (< 0.5)"
+                )
+            )
+        tools_config.fixConfigOption("", "local_dir", old_default, silent)
 
 
 class Memory(object):
@@ -230,17 +258,19 @@
         log.info(_("Memory manager init"))
         self.initialized = defer.Deferred()
         self.host = host
-        self._entities_cache = {} # XXX: keep presence/last resource/other data in cache
-                                  #     /!\ an entity is not necessarily in roster
-                                  #     main key is bare jid, value is a dict
-                                  #     where main key is resource, or None for bare jid
-        self._key_signals = set() # key which need a signal to frontends when updated
+        self._entities_cache = {}  # XXX: keep presence/last resource/other data in cache
+        #     /!\ an entity is not necessarily in roster
+        #     main key is bare jid, value is a dict
+        #     where main key is resource, or None for bare jid
+        self._key_signals = set()  # key which need a signal to frontends when updated
         self.subscriptions = {}
         self.auth_sessions = PasswordSessions()  # remember the authenticated profiles
         self.disco = Discovery(host)
         fixLocalDir(False)  # XXX: tmp update code, will be removed in the future
         self.config = tools_config.parseMainConf()
-        database_file = os.path.expanduser(os.path.join(self.getConfig('', 'local_dir'), C.SAVEFILE_DATABASE))
+        database_file = os.path.expanduser(
+            os.path.join(self.getConfig("", "local_dir"), C.SAVEFILE_DATABASE)
+        )
         self.storage = SqliteStorage(database_file, host.version)
         PersistentDict.storage = self.storage
         self.params = Params(host, self.storage)
@@ -290,7 +320,7 @@
         """
         if not filename:
             return False
-        #TODO: need to encrypt files (at least passwords !) and set permissions
+        # TODO: need to encrypt files (at least passwords !) and set permissions
         filename = os.path.expanduser(filename)
         try:
             self.params.save_xml(filename)
@@ -302,7 +332,7 @@
 
     def load(self):
         """Load parameters and all memory things from db"""
-        #parameters data
+        # parameters data
         return self.params.loadGenParams()
 
     def loadIndividualParams(self, profile):
@@ -340,7 +370,9 @@
                     session_d = self._entities_cache[profile]
                 except KeyError:
                     # else we do request the params
-                    session_d = self._entities_cache[profile] = self.loadIndividualParams(profile)
+                    session_d = self._entities_cache[profile] = self.loadIndividualParams(
+                        profile
+                    )
                     session_d.addCallback(createSession)
                 finally:
                     return session_d
@@ -396,12 +428,20 @@
 
         def check_result(result):
             if not result:
-                log.warning(u'Authentication failure of profile {}'.format(profile))
-                raise failure.Failure(exceptions.PasswordError(u"The provided profile password doesn't match."))
-            if not session_data:  # avoid to create two profile sessions when password if specified
+                log.warning(u"Authentication failure of profile {}".format(profile))
+                raise failure.Failure(
+                    exceptions.PasswordError(
+                        u"The provided profile password doesn't match."
+                    )
+                )
+            if (
+                not session_data
+            ):  # avoid to create two profile sessions when password if specified
                 return self.newAuthSession(password, profile)
 
-        d = self.asyncGetParamA(C.PROFILE_PASS_PATH[1], C.PROFILE_PASS_PATH[0], profile_key=profile)
+        d = self.asyncGetParamA(
+            C.PROFILE_PASS_PATH[1], C.PROFILE_PASS_PATH[0], profile_key=profile
+        )
         d.addCallback(lambda sat_cipher: PasswordHasher.verify(password, sat_cipher))
         return d.addCallback(check_result)
 
@@ -414,10 +454,13 @@
         @param profile: %(doc_profile)s
         @return: a deferred None value
         """
+
         def gotPersonalKey(personal_key):
             """Create the session for this profile and store the personal key"""
-            self.auth_sessions.newSession({C.MEMORY_CRYPTO_KEY: personal_key}, profile=profile)
-            log.debug(u'auth session created for profile %s' % profile)
+            self.auth_sessions.newSession(
+                {C.MEMORY_CRYPTO_KEY: personal_key}, profile=profile
+            )
+            log.debug(u"auth session created for profile %s" % profile)
 
         d = PersistentDict(C.MEMORY_CRYPTO_NAMESPACE, profile).load()
         d.addCallback(lambda data: BlockCipher.decrypt(key, data[C.MEMORY_CRYPTO_KEY]))
@@ -431,7 +474,12 @@
         try:
             del self._entities_cache[profile]
         except KeyError:
-            log.error(_(u"Trying to purge roster status cache for a profile not in memory: [%s]") % profile)
+            log.error(
+                _(
+                    u"Trying to purge roster status cache for a profile not in memory: [%s]"
+                )
+                % profile
+            )
 
     def getProfilesList(self, clients=True, components=False):
         """retrieve profiles list
@@ -472,7 +520,7 @@
         # we want to be sure that the profile exists
         profile = self.getProfileName(profile)
 
-        self.memory_data['Profile_default'] = profile
+        self.memory_data["Profile_default"] = profile
 
     def createProfile(self, name, password, component=None):
         """Create a new profile
@@ -486,9 +534,9 @@
         """
         if not name:
             raise ValueError(u"Empty profile name")
-        if name[0] == '@':
+        if name[0] == "@":
             raise ValueError(u"A profile name can't start with a '@'")
-        if '\n' in name:
+        if "\n" in name:
             raise ValueError(u"A profile name can't contain line feed ('\\n')")
 
         if name in self._entities_cache:
@@ -496,19 +544,28 @@
 
         if component:
             if not component in self.host.plugins:
-                raise exceptions.NotFound(_(u"Can't find component {component} entry point".format(
-                    component = component)))
+                raise exceptions.NotFound(
+                    _(
+                        u"Can't find component {component} entry point".format(
+                            component=component
+                        )
+                    )
+                )
             # FIXME: PLUGIN_INFO is not currently accessible after import, but type shoul be tested here
-            # if self.host.plugins[component].PLUGIN_INFO[u"type"] != C.PLUG_TYPE_ENTRY_POINT:
-            #     raise ValueError(_(u"Plugin {component} is not an entry point !".format(
-            #         component = component)))
+            #  if self.host.plugins[component].PLUGIN_INFO[u"type"] != C.PLUG_TYPE_ENTRY_POINT:
+            #      raise ValueError(_(u"Plugin {component} is not an entry point !".format(
+            #          component = component)))
 
         d = self.params.createProfile(name, component)
 
         def initPersonalKey(dummy):
             # be sure to call this after checking that the profile doesn't exist yet
-            personal_key = BlockCipher.getRandomKey(base64=True)  # generated once for all and saved in a PersistentDict
-            self.auth_sessions.newSession({C.MEMORY_CRYPTO_KEY: personal_key}, profile=name)  # will be encrypted by setParam
+            personal_key = BlockCipher.getRandomKey(
+                base64=True
+            )  # generated once for all and saved in a PersistentDict
+            self.auth_sessions.newSession(
+                {C.MEMORY_CRYPTO_KEY: personal_key}, profile=name
+            )  # will be encrypted by setParam
 
         def startFakeSession(dummy):
             # avoid ProfileNotConnected exception in setParam
@@ -521,7 +578,11 @@
 
         d.addCallback(initPersonalKey)
         d.addCallback(startFakeSession)
-        d.addCallback(lambda dummy: self.setParam(C.PROFILE_PASS_PATH[1], password, C.PROFILE_PASS_PATH[0], profile_key=name))
+        d.addCallback(
+            lambda dummy: self.setParam(
+                C.PROFILE_PASS_PATH[1], password, C.PROFILE_PASS_PATH[0], profile_key=name
+            )
+        )
         d.addCallback(stopFakeSession)
         d.addCallback(lambda dummy: self.auth_sessions.profileDelUnique(name))
         return d
@@ -534,12 +595,14 @@
         To be used for direct calls only (not through the bridge).
         @return: a Deferred instance
         """
+
         def cleanMemory(dummy):
             self.auth_sessions.profileDelUnique(name)
             try:
                 del self._entities_cache[name]
             except KeyError:
                 pass
+
         d = self.params.asyncDeleteProfile(name, force)
         d.addCallback(cleanMemory)
         return d
@@ -567,10 +630,28 @@
     def addToHistory(self, client, data):
         return self.storage.addToHistory(data, client.profile)
 
-    def _historyGet(self, from_jid_s, to_jid_s, limit=C.HISTORY_LIMIT_NONE, between=True, filters=None, profile=C.PROF_KEY_NONE):
-        return self.historyGet(jid.JID(from_jid_s), jid.JID(to_jid_s), limit, between, filters, profile)
+    def _historyGet(
+        self,
+        from_jid_s,
+        to_jid_s,
+        limit=C.HISTORY_LIMIT_NONE,
+        between=True,
+        filters=None,
+        profile=C.PROF_KEY_NONE,
+    ):
+        return self.historyGet(
+            jid.JID(from_jid_s), jid.JID(to_jid_s), limit, between, filters, profile
+        )
 
-    def historyGet(self, from_jid, to_jid, limit=C.HISTORY_LIMIT_NONE, between=True, filters=None, profile=C.PROF_KEY_NONE):
+    def historyGet(
+        self,
+        from_jid,
+        to_jid,
+        limit=C.HISTORY_LIMIT_NONE,
+        between=True,
+        filters=None,
+        profile=C.PROF_KEY_NONE,
+    ):
         """Retrieve messages in history
 
         @param from_jid (JID): source JID (full, or bare for catchall)
@@ -586,7 +667,7 @@
         """
         assert profile != C.PROF_KEY_NONE
         if limit == C.HISTORY_LIMIT_DEFAULT:
-            limit = int(self.getParamA(C.HISTORY_LIMIT, 'General', profile_key=profile))
+            limit = int(self.getParamA(C.HISTORY_LIMIT, "General", profile_key=profile))
         elif limit == C.HISTORY_LIMIT_NONE:
             limit = None
         if limit == 0:
@@ -597,7 +678,7 @@
 
     def _getPresenceStatuses(self, profile_key):
         ret = self.getPresenceStatuses(profile_key)
-        return {entity.full():data for entity, data in ret.iteritems()}
+        return {entity.full(): data for entity, data in ret.iteritems()}
 
     def getPresenceStatuses(self, profile_key):
         """Get all the presence statuses of a profile
@@ -617,7 +698,9 @@
                     presence_data = self.getEntityDatum(full_jid, "presence", profile_key)
                 except KeyError:
                     continue
-                entities_presence.setdefault(entity_jid, {})[resource or ''] = presence_data
+                entities_presence.setdefault(entity_jid, {})[
+                    resource or ""
+                ] = presence_data
 
         return entities_presence
 
@@ -631,7 +714,9 @@
         @param profile_key: %(doc_profile_key)s
         """
         presence_data = PresenceTuple(show, priority, statuses)
-        self.updateEntityData(entity_jid, "presence", presence_data, profile_key=profile_key)
+        self.updateEntityData(
+            entity_jid, "presence", presence_data, profile_key=profile_key
+        )
         if entity_jid.resource and show != C.PRESENCE_UNAVAILABLE:
             # If a resource is available, bare jid should not have presence information
             try:
@@ -657,13 +742,17 @@
         """
         # FIXME: is there a need to keep cache data for resources which are not connected anymore?
         if entity_jid.resource:
-            raise ValueError("getAllResources must be used with a bare jid (got {})".format(entity_jid))
+            raise ValueError(
+                "getAllResources must be used with a bare jid (got {})".format(entity_jid)
+            )
         profile_cache = self._getProfileCache(client)
         try:
             entity_data = profile_cache[entity_jid.userhostJID()]
         except KeyError:
-            raise exceptions.UnknownEntityError(u"Entity {} not in cache".format(entity_jid))
-        resources= set(entity_data.keys())
+            raise exceptions.UnknownEntityError(
+                u"Entity {} not in cache".format(entity_jid)
+            )
+        resources = set(entity_data.keys())
         resources.discard(None)
         return resources
 
@@ -701,7 +790,9 @@
         @return (unicode): main resource or None
         """
         if entity_jid.resource:
-            raise ValueError("getMainResource must be used with a bare jid (got {})".format(entity_jid))
+            raise ValueError(
+                "getMainResource must be used with a bare jid (got {})".format(entity_jid)
+            )
         try:
             if self.host.plugins["XEP-0045"].isJoinedRoom(client, entity_jid):
                 return None  # MUC rooms have no main resource
@@ -766,7 +857,9 @@
                 full_jid.resource = resource
                 yield full_jid
 
-    def updateEntityData(self, entity_jid, key, value, silent=False, profile_key=C.PROF_KEY_NONE):
+    def updateEntityData(
+        self, entity_jid, key, value, silent=False, profile_key=C.PROF_KEY_NONE
+    ):
         """Set a misc data for an entity
 
         If key was registered with setSignalOnUpdate, a signal will be sent to frontends
@@ -780,19 +873,27 @@
         client = self.host.getClient(profile_key)
         profile_cache = self._getProfileCache(client)
         if entity_jid in (C.ENTITY_ALL_RESOURCES, C.ENTITY_ALL):
-            entities = self.getAllEntitiesIter(client, entity_jid==C.ENTITY_ALL)
+            entities = self.getAllEntitiesIter(client, entity_jid == C.ENTITY_ALL)
         else:
             entities = (entity_jid,)
 
         for jid_ in entities:
-            entity_data = profile_cache.setdefault(jid_.userhostJID(),{}).setdefault(jid_.resource, {})
+            entity_data = profile_cache.setdefault(jid_.userhostJID(), {}).setdefault(
+                jid_.resource, {}
+            )
 
             entity_data[key] = value
             if key in self._key_signals and not silent:
                 if not isinstance(value, basestring):
-                    log.error(u"Setting a non string value ({}) for a key ({}) which has a signal flag".format(value, key))
+                    log.error(
+                        u"Setting a non string value ({}) for a key ({}) which has a signal flag".format(
+                            value, key
+                        )
+                    )
                 else:
-                    self.host.bridge.entityDataUpdated(jid_.full(), key, value, self.getProfileName(profile_key))
+                    self.host.bridge.entityDataUpdated(
+                        jid_.full(), key, value, self.getProfileName(profile_key)
+                    )
 
     def delEntityDatum(self, entity_jid, key, profile_key):
         """Delete a data for an entity
@@ -808,7 +909,7 @@
         client = self.host.getClient(profile_key)
         profile_cache = self._getProfileCache(client)
         if entity_jid in (C.ENTITY_ALL_RESOURCES, C.ENTITY_ALL):
-            entities = self.getAllEntitiesIter(client, entity_jid==C.ENTITY_ALL)
+            entities = self.getAllEntitiesIter(client, entity_jid == C.ENTITY_ALL)
         else:
             entities = (entity_jid,)
 
@@ -816,17 +917,21 @@
             try:
                 entity_data = profile_cache[jid_.userhostJID()][jid_.resource]
             except KeyError:
-                raise exceptions.UnknownEntityError(u"Entity {} not in cache".format(jid_))
+                raise exceptions.UnknownEntityError(
+                    u"Entity {} not in cache".format(jid_)
+                )
             try:
                 del entity_data[key]
             except KeyError as e:
                 if entity_jid in (C.ENTITY_ALL_RESOURCES, C.ENTITY_ALL):
-                    continue # we ignore KeyError when deleting keys from several entities
+                    continue  # we ignore KeyError when deleting keys from several entities
                 else:
                     raise e
 
     def _getEntitiesData(self, entities_jids, keys_list, profile_key):
-        ret = self.getEntitiesData([jid.JID(jid_) for jid_ in entities_jids], keys_list, profile_key)
+        ret = self.getEntitiesData(
+            [jid.JID(jid_) for jid_ in entities_jids], keys_list, profile_key
+        )
         return {jid_.full(): data for jid_, data in ret.iteritems()}
 
     def getEntitiesData(self, entities_jids, keys_list=None, profile_key=C.PROF_KEY_NONE):
@@ -843,6 +948,7 @@
 
         @raise exceptions.UnknownEntityError: if entity is not in cache
         """
+
         def fillEntityData(entity_cache_data):
             entity_data = {}
             if keys_list is None:
@@ -861,7 +967,9 @@
         if entities_jids:
             for entity in entities_jids:
                 try:
-                    entity_cache_data = profile_cache[entity.userhostJID()][entity.resource]
+                    entity_cache_data = profile_cache[entity.userhostJID()][
+                        entity.resource
+                    ]
                 except KeyError:
                     continue
                 ret_data[entity.full()] = fillEntityData(entity_cache_data, keys_list)
@@ -891,7 +999,11 @@
         try:
             entity_data = profile_cache[entity_jid.userhostJID()][entity_jid.resource]
         except KeyError:
-            raise exceptions.UnknownEntityError(u"Entity {} not in cache (was requesting {})".format(entity_jid, keys_list))
+            raise exceptions.UnknownEntityError(
+                u"Entity {} not in cache (was requesting {})".format(
+                    entity_jid, keys_list
+                )
+            )
         if keys_list is None:
             return entity_data
 
@@ -910,7 +1022,9 @@
         """
         return self.getEntityData(entity_jid, (key,), profile_key)[key]
 
-    def delEntityCache(self, entity_jid, delete_all_resources=True, profile_key=C.PROF_KEY_NONE):
+    def delEntityCache(
+        self, entity_jid, delete_all_resources=True, profile_key=C.PROF_KEY_NONE
+    ):
         """Remove all cached data for entity
 
         @param entity_jid: JID of the entity to delete
@@ -928,12 +1042,16 @@
             try:
                 del profile_cache[entity_jid]
             except KeyError:
-                raise exceptions.UnknownEntityError(u"Entity {} not in cache".format(entity_jid))
+                raise exceptions.UnknownEntityError(
+                    u"Entity {} not in cache".format(entity_jid)
+                )
         else:
             try:
                 del profile_cache[entity_jid.userhostJID()][entity_jid.resource]
             except KeyError:
-                raise exceptions.UnknownEntityError(u"Entity {} not in cache".format(entity_jid))
+                raise exceptions.UnknownEntityError(
+                    u"Entity {} not in cache".format(entity_jid)
+                )
 
     ## Encryption ##
 
@@ -947,9 +1065,14 @@
         @return: the deferred encrypted value
         """
         try:
-            personal_key = self.auth_sessions.profileGetUnique(profile)[C.MEMORY_CRYPTO_KEY]
+            personal_key = self.auth_sessions.profileGetUnique(profile)[
+                C.MEMORY_CRYPTO_KEY
+            ]
         except TypeError:
-            raise exceptions.InternalError(_('Trying to encrypt a value for %s while the personal key is undefined!') % profile)
+            raise exceptions.InternalError(
+                _("Trying to encrypt a value for %s while the personal key is undefined!")
+                % profile
+            )
         return BlockCipher.encrypt(personal_key, value)
 
     def decryptValue(self, value, profile):
@@ -962,9 +1085,14 @@
         @return: the deferred decrypted value
         """
         try:
-            personal_key = self.auth_sessions.profileGetUnique(profile)[C.MEMORY_CRYPTO_KEY]
+            personal_key = self.auth_sessions.profileGetUnique(profile)[
+                C.MEMORY_CRYPTO_KEY
+            ]
         except TypeError:
-            raise exceptions.InternalError(_('Trying to decrypt a value for %s while the personal key is undefined!') % profile)
+            raise exceptions.InternalError(
+                _("Trying to decrypt a value for %s while the personal key is undefined!")
+                % profile
+            )
         return BlockCipher.decrypt(personal_key, value)
 
     def encryptPersonalData(self, data_key, data_value, crypto_key, profile):
@@ -987,8 +1115,10 @@
             return d.addCallback(cb)
 
         def done(dummy):
-            log.debug(_(u'Personal data (%(ns)s, %(key)s) has been successfuly encrypted') %
-                      {'ns': C.MEMORY_CRYPTO_NAMESPACE, 'key': data_key})
+            log.debug(
+                _(u"Personal data (%(ns)s, %(key)s) has been successfuly encrypted")
+                % {"ns": C.MEMORY_CRYPTO_NAMESPACE, "key": data_key}
+            )
 
         d = PersistentDict(C.MEMORY_CRYPTO_NAMESPACE, profile).load()
         return d.addCallback(gotIndMemory).addCallback(done)
@@ -1014,7 +1144,7 @@
         """Called to get a list of currently waiting subscription requests"""
         profile = self.getProfileName(profile_key)
         if not profile:
-            log.error(_('Asking waiting subscriptions for a non-existant profile'))
+            log.error(_("Asking waiting subscriptions for a non-existant profile"))
             return {}
         if profile not in self.subscriptions:
             return {}
@@ -1029,28 +1159,59 @@
     def getParamA(self, name, category, attr="value", profile_key=C.PROF_KEY_NONE):
         return self.params.getParamA(name, category, attr, profile_key=profile_key)
 
-    def asyncGetParamA(self, name, category, attr="value", security_limit=C.NO_SECURITY_LIMIT, profile_key=C.PROF_KEY_NONE):
-        return self.params.asyncGetParamA(name, category, attr, security_limit, profile_key)
+    def asyncGetParamA(
+        self,
+        name,
+        category,
+        attr="value",
+        security_limit=C.NO_SECURITY_LIMIT,
+        profile_key=C.PROF_KEY_NONE,
+    ):
+        return self.params.asyncGetParamA(
+            name, category, attr, security_limit, profile_key
+        )
 
-    def asyncGetParamsValuesFromCategory(self, category, security_limit=C.NO_SECURITY_LIMIT, profile_key=C.PROF_KEY_NONE):
-        return self.params.asyncGetParamsValuesFromCategory(category, security_limit, profile_key)
+    def asyncGetParamsValuesFromCategory(
+        self, category, security_limit=C.NO_SECURITY_LIMIT, profile_key=C.PROF_KEY_NONE
+    ):
+        return self.params.asyncGetParamsValuesFromCategory(
+            category, security_limit, profile_key
+        )
 
-    def asyncGetStringParamA(self, name, category, attr="value", security_limit=C.NO_SECURITY_LIMIT, profile_key=C.PROF_KEY_NONE):
-        return self.params.asyncGetStringParamA(name, category, attr, security_limit, profile_key)
+    def asyncGetStringParamA(
+        self,
+        name,
+        category,
+        attr="value",
+        security_limit=C.NO_SECURITY_LIMIT,
+        profile_key=C.PROF_KEY_NONE,
+    ):
+        return self.params.asyncGetStringParamA(
+            name, category, attr, security_limit, profile_key
+        )
 
-    def getParamsUI(self, security_limit=C.NO_SECURITY_LIMIT, app='', profile_key=C.PROF_KEY_NONE):
+    def getParamsUI(
+        self, security_limit=C.NO_SECURITY_LIMIT, app="", profile_key=C.PROF_KEY_NONE
+    ):
         return self.params.getParamsUI(security_limit, app, profile_key)
 
     def getParamsCategories(self):
         return self.params.getParamsCategories()
 
-    def setParam(self, name, value, category, security_limit=C.NO_SECURITY_LIMIT, profile_key=C.PROF_KEY_NONE):
+    def setParam(
+        self,
+        name,
+        value,
+        category,
+        security_limit=C.NO_SECURITY_LIMIT,
+        profile_key=C.PROF_KEY_NONE,
+    ):
         return self.params.setParam(name, value, category, security_limit, profile_key)
 
     def updateParams(self, xml):
         return self.params.updateParams(xml)
 
-    def paramsRegisterApp(self, xml, security_limit=C.NO_SECURITY_LIMIT, app=''):
+    def paramsRegisterApp(self, xml, security_limit=C.NO_SECURITY_LIMIT, app=""):
         return self.params.paramsRegisterApp(xml, security_limit, app)
 
     def setDefault(self, name, category, callback, errback=None):
@@ -1073,25 +1234,25 @@
         if peer_jid is None and perms_to_check is None:
             return
         peer_jid = peer_jid.userhostJID()
-        if peer_jid == file_data['owner']:
+        if peer_jid == file_data["owner"]:
             # the owner has all rights
             return
         if not C.ACCESS_PERMS.issuperset(perms_to_check):
-            raise exceptions.InternalError(_(u'invalid permission'))
+            raise exceptions.InternalError(_(u"invalid permission"))
 
         for perm in perms_to_check:
             # we check each perm and raise PermissionError as soon as one condition is not valid
             # we must never return here, we only return after the loop if nothing was blocking the access
             try:
-                perm_data = file_data[u'access'][perm]
-                perm_type = perm_data[u'type']
+                perm_data = file_data[u"access"][perm]
+                perm_type = perm_data[u"type"]
             except KeyError:
                 raise failure.Failure(exceptions.PermissionError())
             if perm_type == C.ACCESS_TYPE_PUBLIC:
                 continue
             elif perm_type == C.ACCESS_TYPE_WHITELIST:
                 try:
-                    jids = perm_data[u'jids']
+                    jids = perm_data[u"jids"]
                 except KeyError:
                     raise failure.Failure(exceptions.PermissionError())
                 if peer_jid.full() in jids:
@@ -1099,7 +1260,9 @@
                 else:
                     raise failure.Failure(exceptions.PermissionError())
             else:
-                raise exceptions.InternalError(_(u'unknown access type: {type}').format(type=perm_type))
+                raise exceptions.InternalError(
+                    _(u"unknown access type: {type}").format(type=perm_type)
+                )
 
     @defer.inlineCallbacks
     def checkPermissionToRoot(self, client, file_data, peer_jid, perms_to_check):
@@ -1107,17 +1270,21 @@
         current = file_data
         while True:
             self.checkFilePermission(current, peer_jid, perms_to_check)
-            parent = current[u'parent']
+            parent = current[u"parent"]
             if not parent:
                 break
-            files_data = yield self.getFile(self, client, peer_jid=None, file_id=parent, perms_to_check=None)
+            files_data = yield self.getFile(
+                self, client, peer_jid=None, file_id=parent, perms_to_check=None
+            )
             try:
                 current = files_data[0]
             except IndexError:
-                raise exceptions.DataError(u'Missing parent')
+                raise exceptions.DataError(u"Missing parent")
 
     @defer.inlineCallbacks
-    def _getParentDir(self, client, path, parent, namespace, owner, peer_jid, perms_to_check):
+    def _getParentDir(
+        self, client, path, parent, namespace, owner, peer_jid, perms_to_check
+    ):
         """Retrieve parent node from a path, or last existing directory
 
         each directory of the path will be retrieved, until the last existing one
@@ -1128,32 +1295,59 @@
         """
         # if path is set, we have to retrieve parent directory of the file(s) from it
         if parent is not None:
-            raise exceptions.ConflictError(_(u"You can't use path and parent at the same time"))
-        path_elts = filter(None, path.split(u'/'))
-        if {u'..', u'.'}.intersection(path_elts):
+            raise exceptions.ConflictError(
+                _(u"You can't use path and parent at the same time")
+            )
+        path_elts = filter(None, path.split(u"/"))
+        if {u"..", u"."}.intersection(path_elts):
             raise ValueError(_(u'".." or "." can\'t be used in path'))
 
         # we retrieve all directories from path until we get the parent container
         # non existing directories will be created
-        parent = u''
+        parent = u""
         for idx, path_elt in enumerate(path_elts):
-            directories = yield self.storage.getFiles(client, parent=parent, type_=C.FILE_TYPE_DIRECTORY,
-                                                      name=path_elt, namespace=namespace, owner=owner)
+            directories = yield self.storage.getFiles(
+                client,
+                parent=parent,
+                type_=C.FILE_TYPE_DIRECTORY,
+                name=path_elt,
+                namespace=namespace,
+                owner=owner,
+            )
             if not directories:
                 defer.returnValue((parent, path_elts[idx:]))
                 # from this point, directories don't exist anymore, we have to create them
             elif len(directories) > 1:
-                raise exceptions.InternalError(_(u"Several directories found, this should not happen"))
+                raise exceptions.InternalError(
+                    _(u"Several directories found, this should not happen")
+                )
             else:
                 directory = directories[0]
                 self.checkFilePermission(directory, peer_jid, perms_to_check)
-                parent = directory[u'id']
+                parent = directory[u"id"]
         defer.returnValue((parent, []))
 
     @defer.inlineCallbacks
-    def getFiles(self, client, peer_jid, file_id=None, version=None, parent=None, path=None, type_=None,
-                 file_hash=None, hash_algo=None, name=None, namespace=None, mime_type=None,
-                 owner=None, access=None, projection=None, unique=False, perms_to_check=(C.ACCESS_PERM_READ,)):
+    def getFiles(
+        self,
+        client,
+        peer_jid,
+        file_id=None,
+        version=None,
+        parent=None,
+        path=None,
+        type_=None,
+        file_hash=None,
+        hash_algo=None,
+        name=None,
+        namespace=None,
+        mime_type=None,
+        owner=None,
+        access=None,
+        projection=None,
+        unique=False,
+        perms_to_check=(C.ACCESS_PERM_READ,),
+    ):
         """retrieve files with with given filters
 
         @param peer_jid(jid.JID, None): jid trying to access the file
@@ -1180,12 +1374,16 @@
             on the path
         """
         if peer_jid is None and perms_to_check or perms_to_check is None and peer_jid:
-            raise exceptions.InternalError('if you want to disable permission check, both peer_jid and perms_to_check must be None')
+            raise exceptions.InternalError(
+                "if you want to disable permission check, both peer_jid and perms_to_check must be None"
+            )
         if owner is not None:
             owner = owner.userhostJID()
         if path is not None:
             # permission are checked by _getParentDir
-            parent, remaining_path_elts = yield self._getParentDir(client, path, parent, namespace, owner, peer_jid, perms_to_check)
+            parent, remaining_path_elts = yield self._getParentDir(
+                client, path, parent, namespace, owner, peer_jid, perms_to_check
+            )
             if remaining_path_elts:
                 # if we have remaining path elements,
                 # the parent directory is not found
@@ -1197,16 +1395,30 @@
             try:
                 parent_data = parent_data[0]
             except IndexError:
-                raise exceptions.DataError(u'mising parent')
-            yield self.checkPermissionToRoot(client, parent_data, peer_jid, perms_to_check)
+                raise exceptions.DataError(u"mising parent")
+            yield self.checkPermissionToRoot(
+                client, parent_data, peer_jid, perms_to_check
+            )
 
-        files = yield self.storage.getFiles(client, file_id=file_id, version=version, parent=parent, type_=type_,
-                                            file_hash=file_hash, hash_algo=hash_algo, name=name, namespace=namespace,
-                                            mime_type=mime_type, owner=owner, access=access,
-                                            projection=projection, unique=unique)
+        files = yield self.storage.getFiles(
+            client,
+            file_id=file_id,
+            version=version,
+            parent=parent,
+            type_=type_,
+            file_hash=file_hash,
+            hash_algo=hash_algo,
+            name=name,
+            namespace=namespace,
+            mime_type=mime_type,
+            owner=owner,
+            access=access,
+            projection=projection,
+            unique=unique,
+        )
 
         if peer_jid:
-            # if permission are checked, we must remove all file tha use can't access
+            #  if permission are checked, we must remove all file tha use can't access
             to_remove = []
             for file_data in files:
                 try:
@@ -1218,10 +1430,28 @@
         defer.returnValue(files)
 
     @defer.inlineCallbacks
-    def setFile(self, client, name, file_id=None, version=u'', parent=None, path=None,
-                type_=C.FILE_TYPE_FILE, file_hash=None, hash_algo=None, size=None, namespace=None,
-                mime_type=None, created=None, modified=None, owner=None, access=None, extra=None,
-                peer_jid = None, perms_to_check=(C.ACCESS_PERM_WRITE,)):
+    def setFile(
+        self,
+        client,
+        name,
+        file_id=None,
+        version=u"",
+        parent=None,
+        path=None,
+        type_=C.FILE_TYPE_FILE,
+        file_hash=None,
+        hash_algo=None,
+        size=None,
+        namespace=None,
+        mime_type=None,
+        created=None,
+        modified=None,
+        owner=None,
+        access=None,
+        extra=None,
+        peer_jid=None,
+        perms_to_check=(C.ACCESS_PERM_WRITE,),
+    ):
         """set a file metadata
 
         @param name(unicode): basename of the file
@@ -1258,12 +1488,17 @@
             if None, permission will no be checked (peer_jid must be None too in this case)
         @param profile(unicode): profile owning the file
         """
-        if '/' in name:
+        if "/" in name:
             raise ValueError('name must not contain a slash ("/")')
         if file_id is None:
             file_id = shortuuid.uuid()
-        if file_hash is not None and hash_algo is None or hash_algo is not None and file_hash is None:
-            raise ValueError('file_hash and hash_algo must be set at the same time')
+        if (
+            file_hash is not None
+            and hash_algo is None
+            or hash_algo is not None
+            and file_hash is None
+        ):
+            raise ValueError("file_hash and hash_algo must be set at the same time")
         if mime_type is None:
             mime_type, file_encoding = mimetypes.guess_type(name)
         if created is None:
@@ -1271,31 +1506,56 @@
         if namespace is not None:
             namespace = namespace.strip() or None
         if type_ == C.FILE_TYPE_DIRECTORY:
-            if any(version, file_hash, size,  mime_type):
-                raise ValueError(u"version, file_hash, size and mime_type can't be set for a directory")
+            if any(version, file_hash, size, mime_type):
+                raise ValueError(
+                    u"version, file_hash, size and mime_type can't be set for a directory"
+                )
         if owner is not None:
             owner = owner.userhostJID()
 
         if path is not None:
             # _getParentDir will check permissions if peer_jid is set, so we use owner
-            parent, remaining_path_elts = yield self._getParentDir(client, path, parent, namespace, owner, owner, perms_to_check)
+            parent, remaining_path_elts = yield self._getParentDir(
+                client, path, parent, namespace, owner, owner, perms_to_check
+            )
             # if remaining directories don't exist, we have to create them
             for new_dir in remaining_path_elts:
                 new_dir_id = shortuuid.uuid()
-                yield self.storage.setFile(client, name=new_dir, file_id=new_dir_id, version=u'', parent=parent,
-                                           type_=C.FILE_TYPE_DIRECTORY, namespace=namespace,
-                                           created=time.time(),
-                                           owner=owner,
-                                           access=access, extra={})
+                yield self.storage.setFile(
+                    client,
+                    name=new_dir,
+                    file_id=new_dir_id,
+                    version=u"",
+                    parent=parent,
+                    type_=C.FILE_TYPE_DIRECTORY,
+                    namespace=namespace,
+                    created=time.time(),
+                    owner=owner,
+                    access=access,
+                    extra={},
+                )
                 parent = new_dir_id
         elif parent is None:
-            parent = u''
+            parent = u""
 
-        yield self.storage.setFile(client, file_id=file_id, version=version, parent=parent, type_=type_,
-                                   file_hash=file_hash, hash_algo=hash_algo, name=name, size=size,
-                                   namespace=namespace, mime_type=mime_type, created=created, modified=modified,
-                                   owner=owner,
-                                   access=access, extra=extra)
+        yield self.storage.setFile(
+            client,
+            file_id=file_id,
+            version=version,
+            parent=parent,
+            type_=type_,
+            file_hash=file_hash,
+            hash_algo=hash_algo,
+            name=name,
+            size=size,
+            namespace=namespace,
+            mime_type=mime_type,
+            created=created,
+            modified=modified,
+            owner=owner,
+            access=access,
+            extra=extra,
+        )
 
     def fileUpdate(self, file_id, column, update_cb):
         """update a file column taking care of race condition
@@ -1318,7 +1578,9 @@
         @return (bool): True if entity is available
         """
         if not entity_jid.resource:
-            return bool(self.getAvailableResources(client, entity_jid)) # is any resource is available, entity is available
+            return bool(
+                self.getAvailableResources(client, entity_jid)
+            )  # is any resource is available, entity is available
         try:
             presence_data = self.getEntityDatum(entity_jid, "presence", client.profile)
         except KeyError:
--- a/sat/memory/params.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/memory/params.py	Wed Jun 27 20:14:46 2018 +0200
@@ -24,6 +24,7 @@
 from sat.memory.crypto import BlockCipher, PasswordHasher
 from xml.dom import minidom, NotFoundErr
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from twisted.internet import defer
 from twisted.python.failure import Failure
@@ -43,17 +44,18 @@
     @return (generator[domish.Element]): <jid/> elements
     """
     for jid_ in jids:
-        jid_elt = domish.Element((None, 'jid'))
+        jid_elt = domish.Element((None, "jid"))
         jid_elt.addContent(jid_.full())
         yield jid_elt
 
 
 class Params(object):
     """This class manage parameters with xml"""
+
     ### TODO: add desciption in params
 
-    #TODO: when priority is changed, a new presence stanza must be emitted
-    #TODO: int type (Priority should be int instead of string)
+    # TODO: when priority is changed, a new presence stanza must be emitted
+    # TODO: int type (Priority should be int instead of string)
     default_xml = u"""
     <params>
     <general>
@@ -77,23 +79,23 @@
     </individual>
     </params>
     """ % {
-        'category_general': D_("General"),
-        'category_connection': D_("Connection"),
-        'history_param': C.HISTORY_LIMIT,
-        'history_label': D_('Chat history limit'),
-        'show_offline_contacts': C.SHOW_OFFLINE_CONTACTS,
-        'show_offline_contacts_label': D_('Show offline contacts'),
-        'show_empty_groups': C.SHOW_EMPTY_GROUPS,
-        'show_empty_groups_label': D_('Show empty groups'),
-        'force_server_param': C.FORCE_SERVER_PARAM,
-        'force_port_param': C.FORCE_PORT_PARAM,
-        'new_account_label': D_("Register new account"),
-        'autoconnect_label': D_('Connect on frontend startup'),
-        'autodisconnect_label': D_('Disconnect on frontend closure'),
+        "category_general": D_("General"),
+        "category_connection": D_("Connection"),
+        "history_param": C.HISTORY_LIMIT,
+        "history_label": D_("Chat history limit"),
+        "show_offline_contacts": C.SHOW_OFFLINE_CONTACTS,
+        "show_offline_contacts_label": D_("Show offline contacts"),
+        "show_empty_groups": C.SHOW_EMPTY_GROUPS,
+        "show_empty_groups_label": D_("Show empty groups"),
+        "force_server_param": C.FORCE_SERVER_PARAM,
+        "force_port_param": C.FORCE_PORT_PARAM,
+        "new_account_label": D_("Register new account"),
+        "autoconnect_label": D_("Connect on frontend startup"),
+        "autodisconnect_label": D_("Disconnect on frontend closure"),
     }
 
     def load_default_params(self):
-        self.dom = minidom.parseString(Params.default_xml.encode('utf-8'))
+        self.dom = minidom.parseString(Params.default_xml.encode("utf-8"))
 
     def _mergeParams(self, source_node, dest_node):
         """Look for every node in source_node and recursively copy them to dest if they don't exists"""
@@ -102,8 +104,9 @@
             ret = {}
             for child in children:
                 if child.nodeType == child.ELEMENT_NODE:
-                    ret[(child.tagName, child.getAttribute('name'))] = child
+                    ret[(child.tagName, child.getAttribute("name"))] = child
             return ret
+
         source_map = getNodesMap(source_node.childNodes)
         dest_map = getNodesMap(dest_node.childNodes)
         source_set = set(source_map.keys())
@@ -120,7 +123,7 @@
     def load_xml(self, xml_file):
         """Load parameters template from xml file"""
         self.dom = minidom.parse(xml_file)
-        default_dom = minidom.parseString(Params.default_xml.encode('utf-8'))
+        default_dom = minidom.parseString(Params.default_xml.encode("utf-8"))
         self._mergeParams(default_dom.documentElement, self.dom.documentElement)
 
     def loadGenParams(self):
@@ -140,7 +143,9 @@
         """
         if cache is None:
             self.params[profile] = {}
-        return self.storage.loadIndParams(self.params[profile] if cache is None else cache, profile)
+        return self.storage.loadIndParams(
+            self.params[profile] if cache is None else cache, profile
+        )
 
     def purgeProfile(self, profile):
         """Remove cache data of a profile
@@ -150,12 +155,14 @@
         try:
             del self.params[profile]
         except KeyError:
-            log.error(_(u"Trying to purge cache of a profile not in memory: [%s]") % profile)
+            log.error(
+                _(u"Trying to purge cache of a profile not in memory: [%s]") % profile
+            )
 
     def save_xml(self, filename):
         """Save parameters template to xml file"""
-        with open(filename, 'wb') as xml_file:
-            xml_file.write(self.dom.toxml('utf-8'))
+        with open(filename, "wb") as xml_file:
+            xml_file.write(self.dom.toxml("utf-8"))
 
     def __init__(self, host, storage):
         log.debug("Parameters init")
@@ -174,7 +181,7 @@
         @return: a Deferred instance
         """
         if self.storage.hasProfile(profile):
-            log.info(_('The profile name already exists'))
+            log.info(_("The profile name already exists"))
             return defer.fail(Failure(exceptions.ConflictError))
         if not self.host.trigger.point("ProfileCreation", profile):
             return defer.fail(Failure(exceptions.CancelError))
@@ -189,7 +196,7 @@
         @return: a Deferred instance
         """
         if not self.storage.hasProfile(profile):
-            log.info(_('Trying to delete an unknown profile'))
+            log.info(_("Trying to delete an unknown profile"))
             return defer.fail(Failure(exceptions.ProfileUnknownError(profile)))
         if self.host.isConnected(profile):
             if force:
@@ -210,22 +217,26 @@
         @raise exceptions.ProfileUnknownError: profile doesn't exists
         @raise exceptions.ProfileNotSetError: if C.PROF_KEY_NONE is used
         """
-        if profile_key == '@DEFAULT@':
-            default = self.host.memory.memory_data.get('Profile_default')
+        if profile_key == "@DEFAULT@":
+            default = self.host.memory.memory_data.get("Profile_default")
             if not default:
-                log.info(_('No default profile, returning first one'))
+                log.info(_("No default profile, returning first one"))
                 try:
-                    default = self.host.memory.memory_data['Profile_default'] = self.storage.getProfilesList()[0]
+                    default = self.host.memory.memory_data[
+                        "Profile_default"
+                    ] = self.storage.getProfilesList()[0]
                 except IndexError:
-                    log.info(_('No profile exist yet'))
+                    log.info(_("No profile exist yet"))
                     raise exceptions.ProfileUnknownError(profile_key)
-            return default  # FIXME: temporary, must use real default value, and fallback to first one if it doesn't exists
+            return (
+                default
+            )  # FIXME: temporary, must use real default value, and fallback to first one if it doesn't exists
         elif profile_key == C.PROF_KEY_NONE:
             raise exceptions.ProfileNotSetError
         elif return_profile_keys and profile_key in [C.PROF_KEY_ALL]:
-            return profile_key # this value must be managed by the caller
+            return profile_key  # this value must be managed by the caller
         if not self.storage.hasProfile(profile_key):
-            log.error(_(u'Trying to access an unknown profile (%s)') % profile_key)
+            log.error(_(u"Trying to access an unknown profile (%s)") % profile_key)
             raise exceptions.ProfileUnknownError(profile_key)
         return profile_key
 
@@ -239,12 +250,12 @@
         """
         for node in parent.childNodes:
             if node.nodeName == tag and node.getAttribute("name") == name:
-                #the node already exists
+                # the node already exists
                 return node
-        #the node is new
+        # the node is new
         return None
 
-    def updateParams(self, xml, security_limit=C.NO_SECURITY_LIMIT, app=''):
+    def updateParams(self, xml, security_limit=C.NO_SECURITY_LIMIT, app=""):
         """import xml in parameters, update if the param already exists
 
         If security_limit is specified and greater than -1, the parameters
@@ -254,7 +265,7 @@
         @param app: name of the frontend registering the parameters or empty value
         """
         # TODO: should word with domish.Element
-        src_parent = minidom.parseString(xml.encode('utf-8')).documentElement
+        src_parent = minidom.parseString(xml.encode("utf-8")).documentElement
 
         def pre_process_app_node(src_parent, security_limit, app):
             """Parameters that are registered from a frontend must be checked"""
@@ -264,17 +275,23 @@
                     to_remove.append(type_node)  # accept individual parameters only
                     continue
                 for cat_node in type_node.childNodes:
-                    if cat_node.nodeName != 'category':
+                    if cat_node.nodeName != "category":
                         to_remove.append(cat_node)
                         continue
-                    to_remove_count = 0  # count the params to be removed from current category
+                    to_remove_count = (
+                        0
+                    )  # count the params to be removed from current category
                     for node in cat_node.childNodes:
-                        if node.nodeName != "param" or not self.checkSecurityLimit(node, security_limit):
+                        if node.nodeName != "param" or not self.checkSecurityLimit(
+                            node, security_limit
+                        ):
                             to_remove.append(node)
                             to_remove_count += 1
                             continue
-                        node.setAttribute('app', app)
-                    if len(cat_node.childNodes) == to_remove_count:  # remove empty category
+                        node.setAttribute("app", app)
+                    if (
+                        len(cat_node.childNodes) == to_remove_count
+                    ):  # remove empty category
                         for dummy in xrange(0, to_remove_count):
                             to_remove.pop()
                         to_remove.append(cat_node)
@@ -283,9 +300,11 @@
 
         def import_node(tgt_parent, src_parent):
             for child in src_parent.childNodes:
-                if child.nodeName == '#text':
+                if child.nodeName == "#text":
                     continue
-                node = self.__get_unique_node(tgt_parent, child.nodeName, child.getAttribute("name"))
+                node = self.__get_unique_node(
+                    tgt_parent, child.nodeName, child.getAttribute("name")
+                )
                 if not node:  # The node is new
                     tgt_parent.appendChild(child.cloneNode(True))
                 else:
@@ -310,23 +329,35 @@
         @param app: name of the frontend registering the parameters
         """
         if not app:
-            log.warning(_(u"Trying to register frontends parameters with no specified app: aborted"))
+            log.warning(
+                _(
+                    u"Trying to register frontends parameters with no specified app: aborted"
+                )
+            )
             return
         if not hasattr(self, "frontends_cache"):
             self.frontends_cache = []
         if app in self.frontends_cache:
-            log.debug(_(u"Trying to register twice frontends parameters for %(app)s: aborted" % {"app": app}))
+            log.debug(
+                _(
+                    u"Trying to register twice frontends parameters for %(app)s: aborted"
+                    % {"app": app}
+                )
+            )
             return
         self.frontends_cache.append(app)
         self.updateParams(xml, security_limit, app)
-        log.debug(u"Frontends parameters registered for %(app)s" % {'app': app})
+        log.debug(u"Frontends parameters registered for %(app)s" % {"app": app})
 
     def __default_ok(self, value, name, category):
-        #FIXME: will not work with individual parameters
+        # FIXME: will not work with individual parameters
         self.setParam(name, value, category)
 
     def __default_ko(self, failure, name, category):
-        log.error(_(u"Can't determine default value for [%(category)s/%(name)s]: %(reason)s") % {'category': category, 'name': name, 'reason': str(failure.value)})
+        log.error(
+            _(u"Can't determine default value for [%(category)s/%(name)s]: %(reason)s")
+            % {"category": category, "name": name, "reason": str(failure.value)}
+        )
 
     def setDefault(self, name, category, callback, errback=None):
         """Set default value of parameter
@@ -337,19 +368,27 @@
         @param callback: must return a string with the value (use deferred if needed)
         @param errback: must manage the error with args failure, name, category
         """
-        #TODO: send signal param update if value changed
-        #TODO: manage individual paramaters
-        log.debug ("setDefault called for %(category)s/%(name)s" % {"category": category, "name": name})
-        node = self._getParamNode(name, category, '@ALL@')
+        # TODO: send signal param update if value changed
+        # TODO: manage individual paramaters
+        log.debug(
+            "setDefault called for %(category)s/%(name)s"
+            % {"category": category, "name": name}
+        )
+        node = self._getParamNode(name, category, "@ALL@")
         if not node:
-            log.error(_(u"Requested param [%(name)s] in category [%(category)s] doesn't exist !") % {'name': name, 'category': category})
+            log.error(
+                _(
+                    u"Requested param [%(name)s] in category [%(category)s] doesn't exist !"
+                )
+                % {"name": name, "category": category}
+            )
             return
-        if node[1].getAttribute('default_cb') == 'yes':
+        if node[1].getAttribute("default_cb") == "yes":
             # del node[1].attributes['default_cb'] # default_cb is not used anymore as a flag to know if we have to set the default value,
-                                                   # and we can still use it later e.g. to call a generic setDefault method
+            # and we can still use it later e.g. to call a generic setDefault method
             value = self._getParam(category, name, C.GENERAL)
-            if value is None: # no value set by the user: we have the default value
-                log.debug ("Default value to set, using callback")
+            if value is None:  # no value set by the user: we have the default value
+                log.debug("Default value to set, using callback")
                 d = defer.maybeDeferred(callback)
                 d.addCallback(self.__default_ok, name, category)
                 d.addErrback(errback or self.__default_ko, name, category)
@@ -364,30 +403,61 @@
         @param value: user defined value
         @return: value (can be str, bool, int, list, None)
         """
-        if attr == 'value':
-            value_to_use = value if value is not None else node.getAttribute(attr)  # we use value (user defined) if it exist, else we use node's default value
-            if node.getAttribute('type') == 'bool':
+        if attr == "value":
+            value_to_use = (
+                value if value is not None else node.getAttribute(attr)
+            )  # we use value (user defined) if it exist, else we use node's default value
+            if node.getAttribute("type") == "bool":
                 return C.bool(value_to_use)
-            if node.getAttribute('type') == 'int':
+            if node.getAttribute("type") == "int":
                 return int(value_to_use)
-            elif node.getAttribute('type') == 'list':
-                if not value_to_use:  # no user defined value, take default value from the XML
-                    options = [option for option in node.childNodes if option.nodeName == 'option']
-                    selected = [option for option in options if option.getAttribute('selected') == 'true']
-                    cat, param = node.parentNode.getAttribute('name'), node.getAttribute('name')
+            elif node.getAttribute("type") == "list":
+                if (
+                    not value_to_use
+                ):  # no user defined value, take default value from the XML
+                    options = [
+                        option
+                        for option in node.childNodes
+                        if option.nodeName == "option"
+                    ]
+                    selected = [
+                        option
+                        for option in options
+                        if option.getAttribute("selected") == "true"
+                    ]
+                    cat, param = (
+                        node.parentNode.getAttribute("name"),
+                        node.getAttribute("name"),
+                    )
                     if len(selected) == 1:
-                        value_to_use = selected[0].getAttribute('value')
-                        log.info(_("Unset parameter (%(cat)s, %(param)s) of type list will use the default option '%(value)s'") %
-                                 {'cat': cat, 'param': param, 'value': value_to_use})
+                        value_to_use = selected[0].getAttribute("value")
+                        log.info(
+                            _(
+                                "Unset parameter (%(cat)s, %(param)s) of type list will use the default option '%(value)s'"
+                            )
+                            % {"cat": cat, "param": param, "value": value_to_use}
+                        )
                         return value_to_use
                     if len(selected) == 0:
-                        log.error(_(u'Parameter (%(cat)s, %(param)s) of type list has no default option!') % {'cat': cat, 'param': param})
+                        log.error(
+                            _(
+                                u"Parameter (%(cat)s, %(param)s) of type list has no default option!"
+                            )
+                            % {"cat": cat, "param": param}
+                        )
                     else:
-                        log.error(_(u'Parameter (%(cat)s, %(param)s) of type list has more than one default option!') % {'cat': cat, 'param': param})
+                        log.error(
+                            _(
+                                u"Parameter (%(cat)s, %(param)s) of type list has more than one default option!"
+                            )
+                            % {"cat": cat, "param": param}
+                        )
                     raise exceptions.DataError
-            elif node.getAttribute('type') == 'jids_list':
+            elif node.getAttribute("type") == "jids_list":
                 if value_to_use:
-                    jids = value_to_use.split('\t')  # FIXME: it's not good to use tabs as separator !
+                    jids = value_to_use.split(
+                        "\t"
+                    )  # FIXME: it's not good to use tabs as separator !
                 else:  # no user defined value, take default value from the XML
                     jids = [getText(jid_) for jid_ in node.getElementsByTagName("jid")]
                 to_delete = []
@@ -395,7 +465,9 @@
                     try:
                         jids[idx] = jid.JID(value)
                     except (RuntimeError, jid.InvalidFormat, AttributeError):
-                        log.warning(u"Incorrect jid value found in jids list: [{}]".format(value))
+                        log.warning(
+                            u"Incorrect jid value found in jids list: [{}]".format(value)
+                        )
                         to_delete.append(value)
                 for value in to_delete:
                     jids.remove(value)
@@ -412,8 +484,10 @@
         @param value: user defined value
         @return (unicode, bool, int, list): value to retrieve
         """
-        if attr == 'value' and node.getAttribute('type') == 'password':
-            raise exceptions.InternalError('To retrieve password values, use _asyncGetAttr instead of _getAttr')
+        if attr == "value" and node.getAttribute("type") == "password":
+            raise exceptions.InternalError(
+                "To retrieve password values, use _asyncGetAttr instead of _getAttr"
+            )
         return self._getAttr_internal(node, attr, value)
 
     def _asyncGetAttr(self, node, attr, value, profile=None):
@@ -428,19 +502,27 @@
         @return (unicode, bool, int, list): Deferred value to retrieve
         """
         value = self._getAttr_internal(node, attr, value)
-        if attr != 'value' or node.getAttribute('type') != 'password':
+        if attr != "value" or node.getAttribute("type") != "password":
             return defer.succeed(value)
-        param_cat = node.parentNode.getAttribute('name')
-        param_name = node.getAttribute('name')
+        param_cat = node.parentNode.getAttribute("name")
+        param_name = node.getAttribute("name")
         if ((param_cat, param_name) == C.PROFILE_PASS_PATH) or not value:
-            return defer.succeed(value)  # profile password and empty passwords are returned "as is"
+            return defer.succeed(
+                value
+            )  # profile password and empty passwords are returned "as is"
         if not profile:
-            raise exceptions.ProfileNotSetError('The profile is needed to decrypt a password')
+            raise exceptions.ProfileNotSetError(
+                "The profile is needed to decrypt a password"
+            )
         d = self.host.memory.decryptValue(value, profile)
 
         def gotPlainPassword(password):
-            if password is None:  # empty value means empty password, None means decryption failure
-                raise exceptions.InternalError(_('The stored password could not be decrypted!'))
+            if (
+                password is None
+            ):  # empty value means empty password, None means decryption failure
+                raise exceptions.InternalError(
+                    _("The stored password could not be decrypted!")
+                )
             return password
 
         return d.addCallback(gotPlainPassword)
@@ -455,9 +537,13 @@
 
     def getStringParamA(self, name, category, attr="value", profile_key=C.PROF_KEY_NONE):
         """ Same as getParamA but for bridge: convert non string value to string """
-        return self.__type_to_string(self.getParamA(name, category, attr, profile_key=profile_key))
+        return self.__type_to_string(
+            self.getParamA(name, category, attr, profile_key=profile_key)
+        )
 
-    def getParamA(self, name, category, attr="value", use_default=True, profile_key=C.PROF_KEY_NONE):
+    def getParamA(
+        self, name, category, attr="value", use_default=True, profile_key=C.PROF_KEY_NONE
+    ):
         """Helper method to get a specific attribute.
 
         /!\ This method would return encrypted password values,
@@ -474,15 +560,22 @@
         # FIXME: security_limit is not managed here !
         node = self._getParamNode(name, category)
         if not node:
-            log.error(_(u"Requested param [%(name)s] in category [%(category)s] doesn't exist !") % {'name': name, 'category': category})
+            log.error(
+                _(
+                    u"Requested param [%(name)s] in category [%(category)s] doesn't exist !"
+                )
+                % {"name": name, "category": category}
+            )
             raise exceptions.NotFound
 
-        if attr == 'value' and node[1].getAttribute('type') == 'password':
-            raise exceptions.InternalError('To retrieve password values, use asyncGetParamA instead of getParamA')
+        if attr == "value" and node[1].getAttribute("type") == "password":
+            raise exceptions.InternalError(
+                "To retrieve password values, use asyncGetParamA instead of getParamA"
+            )
 
         if node[0] == C.GENERAL:
             value = self._getParam(category, name, C.GENERAL)
-            if value is None and attr=='value' and not use_default:
+            if value is None and attr == "value" and not use_default:
                 return value
             return self._getAttr(node[1], attr, value)
 
@@ -490,25 +583,39 @@
 
         profile = self.getProfileName(profile_key)
         if not profile:
-            log.error(_('Requesting a param for an non-existant profile'))
+            log.error(_("Requesting a param for an non-existant profile"))
             raise exceptions.ProfileUnknownError(profile_key)
 
         if profile not in self.params:
-            log.error(_('Requesting synchronous param for not connected profile'))
+            log.error(_("Requesting synchronous param for not connected profile"))
             raise exceptions.ProfileNotConnected(profile)
 
         if attr == "value":
             value = self._getParam(category, name, profile=profile)
-            if value is None and attr=='value' and not use_default:
+            if value is None and attr == "value" and not use_default:
                 return value
             return self._getAttr(node[1], attr, value)
 
-    def asyncGetStringParamA(self, name, category, attr="value", security_limit=C.NO_SECURITY_LIMIT, profile_key=C.PROF_KEY_NONE):
+    def asyncGetStringParamA(
+        self,
+        name,
+        category,
+        attr="value",
+        security_limit=C.NO_SECURITY_LIMIT,
+        profile_key=C.PROF_KEY_NONE,
+    ):
         d = self.asyncGetParamA(name, category, attr, security_limit, profile_key)
         d.addCallback(self.__type_to_string)
         return d
 
-    def asyncGetParamA(self, name, category, attr="value", security_limit=C.NO_SECURITY_LIMIT, profile_key=C.PROF_KEY_NONE):
+    def asyncGetParamA(
+        self,
+        name,
+        category,
+        attr="value",
+        security_limit=C.NO_SECURITY_LIMIT,
+        profile_key=C.PROF_KEY_NONE,
+    ):
         """Helper method to get a specific attribute.
 
         @param name: name of the parameter
@@ -519,12 +626,21 @@
         """
         node = self._getParamNode(name, category)
         if not node:
-            log.error(_(u"Requested param [%(name)s] in category [%(category)s] doesn't exist !") % {'name': name, 'category': category})
+            log.error(
+                _(
+                    u"Requested param [%(name)s] in category [%(category)s] doesn't exist !"
+                )
+                % {"name": name, "category": category}
+            )
             raise ValueError("Requested param doesn't exist")
 
         if not self.checkSecurityLimit(node[1], security_limit):
-            log.warning(_(u"Trying to get parameter '%(param)s' in category '%(cat)s' without authorization!!!"
-                      % {'param': name, 'cat': category}))
+            log.warning(
+                _(
+                    u"Trying to get parameter '%(param)s' in category '%(cat)s' without authorization!!!"
+                    % {"param": name, "cat": category}
+                )
+            )
             raise exceptions.PermissionError
 
         if node[0] == C.GENERAL:
@@ -535,7 +651,9 @@
 
         profile = self.getProfileName(profile_key)
         if not profile:
-            raise exceptions.InternalError(_('Requesting a param for a non-existant profile'))
+            raise exceptions.InternalError(
+                _("Requesting a param for a non-existant profile")
+            )
 
         if attr != "value":
             return defer.succeed(node[1].getAttribute(attr))
@@ -543,9 +661,11 @@
             value = self._getParam(category, name, profile=profile)
             return self._asyncGetAttr(node[1], attr, value, profile)
         except exceptions.ProfileNotInCacheError:
-            #We have to ask data to the storage manager
+            # We have to ask data to the storage manager
             d = self.storage.getIndParam(category, name, profile)
-            return d.addCallback(lambda value: self._asyncGetAttr(node[1], attr, value, profile))
+            return d.addCallback(
+                lambda value: self._asyncGetAttr(node[1], attr, value, profile)
+            )
 
     def asyncGetParamsValuesFromCategory(self, category, security_limit, profile_key):
         """Get all parameters "attribute" for a category
@@ -557,7 +677,7 @@
         @param profile_key: %(doc_profile_key)s
         @return (dict): key: param name, value: param value (converted to string if needed)
         """
-        #TODO: manage category of general type (without existant profile)
+        # TODO: manage category of general type (without existant profile)
         profile = self.getProfileName(profile_key)
         if not profile:
             log.error(_("Asking params for inexistant profile"))
@@ -572,11 +692,20 @@
             for category_node in prof_xml.getElementsByTagName("category"):
                 if category_node.getAttribute("name") == category:
                     for param_node in category_node.getElementsByTagName("param"):
-                        name = param_node.getAttribute('name')
+                        name = param_node.getAttribute("name")
                         if not name:
-                            log.warning(u"ignoring attribute without name: {}".format(param_node.toxml()))
+                            log.warning(
+                                u"ignoring attribute without name: {}".format(
+                                    param_node.toxml()
+                                )
+                            )
                             continue
-                        d = self.asyncGetStringParamA(name, category, security_limit=security_limit, profile_key=profile)
+                        d = self.asyncGetStringParamA(
+                            name,
+                            category,
+                            security_limit=security_limit,
+                            profile_key=profile,
+                        )
                         d.addCallback(setValue, ret, name)
                         names_d_list.append(d)
                     break
@@ -586,10 +715,12 @@
             dlist.addCallback(lambda dummy: ret)
             return ret
 
-        d = self._constructProfileXml(security_limit, '', profile)
+        d = self._constructProfileXml(security_limit, "", profile)
         return d.addCallback(returnCategoryXml)
 
-    def _getParam(self, category, name, type_=C.INDIVIDUAL, cache=None, profile=C.PROF_KEY_NONE):
+    def _getParam(
+        self, category, name, type_=C.INDIVIDUAL, cache=None, profile=C.PROF_KEY_NONE
+    ):
         """Return the param, or None if it doesn't exist
 
         @param category: param category
@@ -608,8 +739,10 @@
             raise exceptions.ProfileNotSetError
         if profile in self.params:
             cache = self.params[profile]  # if profile is in main cache, we use it,
-                                          # ignoring the temporary cache
-        elif cache is None:  # else we use the temporary cache if it exists, or raise an exception
+            # ignoring the temporary cache
+        elif (
+            cache is None
+        ):  # else we use the temporary cache if it exists, or raise an exception
             raise exceptions.ProfileNotInCacheError
         if (category, name) not in cache:
             return None
@@ -629,11 +762,13 @@
 
         def checkNode(node):
             """Check the node against security_limit and app"""
-            return self.checkSecurityLimit(node, security_limit) and self.checkApp(node, app)
+            return self.checkSecurityLimit(node, security_limit) and self.checkApp(
+                node, app
+            )
 
         def constructProfile(ignore, profile_cache):
             # init the result document
-            prof_xml = minidom.parseString('<params/>')
+            prof_xml = minidom.parseString("<params/>")
             cache = {}
 
             for type_node in self.dom.documentElement.childNodes:
@@ -641,9 +776,9 @@
                     continue
                 # we use all params, general and individual
                 for cat_node in type_node.childNodes:
-                    if cat_node.nodeName != 'category':
+                    if cat_node.nodeName != "category":
                         continue
-                    category = cat_node.getAttribute('name')
+                    category = cat_node.getAttribute("name")
                     dest_params = {}  # result (merged) params for category
                     if category not in cache:
                         # we make a copy for the new xml
@@ -655,7 +790,7 @@
                             if not checkNode(node):
                                 to_remove.append(node)
                                 continue
-                            dest_params[node.getAttribute('name')] = node
+                            dest_params[node.getAttribute("name")] = node
                         for node in to_remove:
                             dest_cat.removeChild(node)
                         new_node = True
@@ -668,7 +803,7 @@
                     for param_node in params:
                         # we have to merge new params (we are parsing individual parameters, we have to add them
                         # to the previously parsed general ones)
-                        name = param_node.getAttribute('name')
+                        name = param_node.getAttribute("name")
                         if not checkNode(param_node):
                             continue
                         if name not in dest_params:
@@ -676,35 +811,53 @@
                             dest_params[name] = param_node.cloneNode(True)
                             dest_cat.appendChild(dest_params[name])
 
-                        profile_value = self._getParam(category,
-                                                        name, type_node.nodeName,
-                                                        cache=profile_cache, profile=profile)
+                        profile_value = self._getParam(
+                            category,
+                            name,
+                            type_node.nodeName,
+                            cache=profile_cache,
+                            profile=profile,
+                        )
                         if profile_value is not None:
                             # there is a value for this profile, we must change the default
-                            if dest_params[name].getAttribute('type') == 'list':
-                                for option in dest_params[name].getElementsByTagName("option"):
-                                    if option.getAttribute('value') == profile_value:
-                                        option.setAttribute('selected', 'true')
+                            if dest_params[name].getAttribute("type") == "list":
+                                for option in dest_params[name].getElementsByTagName(
+                                    "option"
+                                ):
+                                    if option.getAttribute("value") == profile_value:
+                                        option.setAttribute("selected", "true")
                                     else:
                                         try:
-                                            option.removeAttribute('selected')
+                                            option.removeAttribute("selected")
                                         except NotFoundErr:
                                             pass
-                            elif dest_params[name].getAttribute('type') == 'jids_list':
-                                jids = profile_value.split('\t')
-                                for jid_elt in dest_params[name].getElementsByTagName("jid"):
-                                    dest_params[name].removeChild(jid_elt)  # remove all default
+                            elif dest_params[name].getAttribute("type") == "jids_list":
+                                jids = profile_value.split("\t")
+                                for jid_elt in dest_params[name].getElementsByTagName(
+                                    "jid"
+                                ):
+                                    dest_params[name].removeChild(
+                                        jid_elt
+                                    )  # remove all default
                                 for jid_ in jids:  # rebuilt the children with use values
                                     try:
                                         jid.JID(jid_)
-                                    except (RuntimeError, jid.InvalidFormat, AttributeError):
-                                        log.warning(u"Incorrect jid value found in jids list: [{}]".format(jid_))
+                                    except (
+                                        RuntimeError,
+                                        jid.InvalidFormat,
+                                        AttributeError,
+                                    ):
+                                        log.warning(
+                                            u"Incorrect jid value found in jids list: [{}]".format(
+                                                jid_
+                                            )
+                                        )
                                     else:
-                                        jid_elt = prof_xml.createElement('jid')
+                                        jid_elt = prof_xml.createElement("jid")
                                         jid_elt.appendChild(prof_xml.createTextNode(jid_))
                                         dest_params[name].appendChild(jid_elt)
                             else:
-                                dest_params[name].setAttribute('value', profile_value)
+                                dest_params[name].setAttribute("value", profile_value)
                     if new_node:
                         prof_xml.documentElement.appendChild(dest_cat)
 
@@ -721,7 +874,7 @@
             d = defer.succeed(None)
             profile_cache = self.params[profile]
         else:
-            #profile is not in cache, we load values in a short time cache
+            # profile is not in cache, we load values in a short time cache
             profile_cache = {}
             d = self.loadIndParams(profile, profile_cache)
 
@@ -761,9 +914,11 @@
         def returnXML(prof_xml):
             return_xml = prof_xml.toxml()
             prof_xml.unlink()
-            return '\n'.join((line for line in return_xml.split('\n') if line))
+            return "\n".join((line for line in return_xml.split("\n") if line))
 
-        return self._constructProfileXml(security_limit, app, profile).addCallback(returnXML)
+        return self._constructProfileXml(security_limit, app, profile).addCallback(
+            returnXML
+        )
 
     def _getParamNode(self, name, category, type_="@ALL@"):  # FIXME: is type_ useful ?
         """Return a node from the param_xml
@@ -776,9 +931,14 @@
         @return: a tuple (node type, node) or None if not found"""
 
         for type_node in self.dom.documentElement.childNodes:
-            if (((type_ == "@ALL@" or type_ == "@GENERAL@") and type_node.nodeName == C.GENERAL)
-                    or ((type_ == "@ALL@" or type_ == "@INDIVIDUAL@") and type_node.nodeName == C.INDIVIDUAL)):
-                for node in type_node.getElementsByTagName('category'):
+            if (
+                (type_ == "@ALL@" or type_ == "@GENERAL@")
+                and type_node.nodeName == C.GENERAL
+            ) or (
+                (type_ == "@ALL@" or type_ == "@INDIVIDUAL@")
+                and type_node.nodeName == C.INDIVIDUAL
+            ):
+                for node in type_node.getElementsByTagName("category"):
                     if node.getAttribute("name") == category:
                         params = node.getElementsByTagName("param")
                         for param in params:
@@ -795,7 +955,14 @@
                 categories.append(cat.getAttribute("name"))
         return categories
 
-    def setParam(self, name, value, category, security_limit=C.NO_SECURITY_LIMIT, profile_key=C.PROF_KEY_NONE):
+    def setParam(
+        self,
+        name,
+        value,
+        category,
+        security_limit=C.NO_SECURITY_LIMIT,
+        profile_key=C.PROF_KEY_NONE,
+    ):
         """Set a parameter, return None if the parameter is not in param xml.
 
         Parameter of type 'password' that are not the SàT profile password are
@@ -813,42 +980,59 @@
         if profile_key != C.PROF_KEY_NONE:
             profile = self.getProfileName(profile_key)
             if not profile:
-                log.error(_(u'Trying to set parameter for an unknown profile'))
+                log.error(_(u"Trying to set parameter for an unknown profile"))
                 raise exceptions.ProfileUnknownError(profile_key)
 
-        node = self._getParamNode(name, category, '@ALL@')
+        node = self._getParamNode(name, category, "@ALL@")
         if not node:
-            log.error(_(u'Requesting an unknown parameter (%(category)s/%(name)s)')
-                      % {'category': category, 'name': name})
+            log.error(
+                _(u"Requesting an unknown parameter (%(category)s/%(name)s)")
+                % {"category": category, "name": name}
+            )
             return defer.succeed(None)
 
         if not self.checkSecurityLimit(node[1], security_limit):
-            log.warning(_(u"Trying to set parameter '%(param)s' in category '%(cat)s' without authorization!!!"
-                          % {'param': name, 'cat': category}))
+            log.warning(
+                _(
+                    u"Trying to set parameter '%(param)s' in category '%(cat)s' without authorization!!!"
+                    % {"param": name, "cat": category}
+                )
+            )
             return defer.succeed(None)
 
         type_ = node[1].getAttribute("type")
-        if type_ == 'int':
+        if type_ == "int":
             if not value:  # replace with the default value (which might also be '')
                 value = node[1].getAttribute("value")
             else:
                 try:
                     int(value)
                 except ValueError:
-                    log.debug(_(u"Trying to set parameter '%(param)s' in category '%(cat)s' with an non-integer value"
-                                % {'param': name, 'cat': category}))
+                    log.debug(
+                        _(
+                            u"Trying to set parameter '%(param)s' in category '%(cat)s' with an non-integer value"
+                            % {"param": name, "cat": category}
+                        )
+                    )
                     return defer.succeed(None)
                 if node[1].hasAttribute("constraint"):
                     constraint = node[1].getAttribute("constraint")
                     try:
                         min_, max_ = [int(limit) for limit in constraint.split(";")]
                     except ValueError:
-                        raise exceptions.InternalError("Invalid integer parameter constraint: %s" % constraint)
+                        raise exceptions.InternalError(
+                            "Invalid integer parameter constraint: %s" % constraint
+                        )
                     value = str(min(max(int(value), min_), max_))
 
-
-        log.info(_("Setting parameter (%(category)s, %(name)s) = %(value)s") %
-                 {'category': category, 'name': name, 'value': value if type_ != 'password' else '********'})
+        log.info(
+            _("Setting parameter (%(category)s, %(name)s) = %(value)s")
+            % {
+                "category": category,
+                "name": name,
+                "value": value if type_ != "password" else "********",
+            }
+        )
 
         if node[0] == C.GENERAL:
             self.params_gen[(category, name)] = value
@@ -856,7 +1040,9 @@
             for profile in self.storage.getProfilesList():
                 if self.host.memory.isSessionStarted(profile):
                     self.host.bridge.paramUpdate(name, value, category, profile)
-                    self.host.trigger.point("paramUpdateTrigger", name, value, category, node[0], profile)
+                    self.host.trigger.point(
+                        "paramUpdateTrigger", name, value, category, node[0], profile
+                    )
             return defer.succeed(None)
 
         assert node[0] == C.INDIVIDUAL
@@ -867,16 +1053,24 @@
             return defer.succeed(None)
         elif type_ == "password":
             try:
-                personal_key = self.host.memory.auth_sessions.profileGetUnique(profile)[C.MEMORY_CRYPTO_KEY]
+                personal_key = self.host.memory.auth_sessions.profileGetUnique(profile)[
+                    C.MEMORY_CRYPTO_KEY
+                ]
             except TypeError:
-                raise exceptions.InternalError(_('Trying to encrypt a password while the personal key is undefined!'))
+                raise exceptions.InternalError(
+                    _("Trying to encrypt a password while the personal key is undefined!")
+                )
             if (category, name) == C.PROFILE_PASS_PATH:
                 # using 'value' as the encryption key to encrypt another encryption key... could be confusing!
-                d = self.host.memory.encryptPersonalData(data_key=C.MEMORY_CRYPTO_KEY,
-                                                         data_value=personal_key,
-                                                         crypto_key=value,
-                                                         profile=profile)
-                d.addCallback(lambda dummy: PasswordHasher.hash(value))  # profile password is hashed (empty value stays empty)
+                d = self.host.memory.encryptPersonalData(
+                    data_key=C.MEMORY_CRYPTO_KEY,
+                    data_value=personal_key,
+                    crypto_key=value,
+                    profile=profile,
+                )
+                d.addCallback(
+                    lambda dummy: PasswordHasher.hash(value)
+                )  # profile password is hashed (empty value stays empty)
             elif value:  # other non empty passwords are encrypted with the personal key
                 d = BlockCipher.encrypt(personal_key, value)
             else:
@@ -888,7 +1082,9 @@
             if self.host.memory.isSessionStarted(profile):
                 self.params[profile][(category, name)] = value
                 self.host.bridge.paramUpdate(name, value, category, profile)
-                self.host.trigger.point("paramUpdateTrigger", name, value, category, node[0], profile)
+                self.host.trigger.point(
+                    "paramUpdateTrigger", name, value, category, node[0], profile
+                )
                 return self.storage.setIndParam(category, name, value, profile)
             else:
                 raise exceptions.ProfileNotConnected
@@ -912,10 +1108,15 @@
         """
         ret = {}
         for type_node in self.dom.documentElement.childNodes:
-            if (((node_type == "@ALL@" or node_type == "@GENERAL@") and type_node.nodeName == C.GENERAL) or
-                ((node_type == "@ALL@" or node_type == "@INDIVIDUAL@") and type_node.nodeName == C.INDIVIDUAL)):
-                for cat_node in type_node.getElementsByTagName('category'):
-                    cat = cat_node.getAttribute('name')
+            if (
+                (node_type == "@ALL@" or node_type == "@GENERAL@")
+                and type_node.nodeName == C.GENERAL
+            ) or (
+                (node_type == "@ALL@" or node_type == "@INDIVIDUAL@")
+                and type_node.nodeName == C.INDIVIDUAL
+            ):
+                for cat_node in type_node.getElementsByTagName("category"):
+                    cat = cat_node.getAttribute("name")
                     params = cat_node.getElementsByTagName("param")
                     for param in params:
                         if param.getAttribute("type") == attr_type:
--- a/sat/plugins/__init__.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/__init__.py	Wed Jun 27 20:14:46 2018 +0200
@@ -2,6 +2,7 @@
 # XXX: the Monkey Patch is here and not in src/__init__ to avoir issues with pyjamas compilation
 import wokkel
 from sat_tmp.wokkel import pubsub as tmp_pubsub, rsm as tmp_rsm, mam as tmp_mam
+
 wokkel.pubsub = tmp_pubsub
 wokkel.rsm = tmp_rsm
 wokkel.mam = tmp_mam
--- a/sat/plugins/plugin_adhoc_dbus.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_adhoc_dbus.py	Wed Jun 27 20:14:46 2018 +0200
@@ -20,18 +20,23 @@
 from sat.core.i18n import _
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from sat.core import exceptions
 from twisted.internet import defer
 from wokkel import data_form
+
 try:
     from lxml import etree
 except ImportError:
-    raise exceptions.MissingModule(u"Missing module lxml, please download/install it from http://lxml.de/")
+    raise exceptions.MissingModule(
+        u"Missing module lxml, please download/install it from http://lxml.de/"
+    )
 import os.path
 import uuid
 import dbus
 from dbus.mainloop.glib import DBusGMainLoop
+
 DBusGMainLoop(set_as_default=True)
 
 FD_NAME = "org.freedesktop.DBus"
@@ -39,8 +44,12 @@
 INTROSPECT_IFACE = "org.freedesktop.DBus.Introspectable"
 
 INTROSPECT_METHOD = "Introspect"
-IGNORED_IFACES_START = ('org.freedesktop', 'org.qtproject', 'org.kde.KMainWindow') # commands in interface starting with these values will be ignored
-FLAG_LOOP = 'LOOP'
+IGNORED_IFACES_START = (
+    "org.freedesktop",
+    "org.qtproject",
+    "org.kde.KMainWindow",
+)  # commands in interface starting with these values will be ignored
+FLAG_LOOP = "LOOP"
 
 PLUGIN_INFO = {
     C.PI_NAME: "Ad-Hoc Commands - D-Bus",
@@ -50,21 +59,25 @@
     C.PI_DEPENDENCIES: ["XEP-0050"],
     C.PI_MAIN: "AdHocDBus",
     C.PI_HANDLER: "no",
-    C.PI_DESCRIPTION: _("""Add D-Bus management to Ad-Hoc commands""")
+    C.PI_DESCRIPTION: _("""Add D-Bus management to Ad-Hoc commands"""),
 }
 
 
 class AdHocDBus(object):
-
     def __init__(self, host):
         log.info(_("plugin Ad-Hoc D-Bus initialization"))
         self.host = host
-        host.bridge.addMethod("adHocDBusAddAuto", ".plugin", in_sign='sasasasasasass', out_sign='(sa(sss))',
-                              method=self._adHocDBusAddAuto,
-                              async=True)
+        host.bridge.addMethod(
+            "adHocDBusAddAuto",
+            ".plugin",
+            in_sign="sasasasasasass",
+            out_sign="(sa(sss))",
+            method=self._adHocDBusAddAuto,
+            async=True,
+        )
         self.session_bus = dbus.SessionBus()
         self.fd_object = self.session_bus.get_object(FD_NAME, FD_PATH, introspect=False)
-        self.XEP_0050 = host.plugins['XEP-0050']
+        self.XEP_0050 = host.plugins["XEP-0050"]
 
     def _DBusAsyncCall(self, proxy, method, *args, **kwargs):
         """ Call a DBus method asynchronously and return a deferred
@@ -77,9 +90,9 @@
 
         """
         d = defer.Deferred()
-        interface = kwargs.pop('interface', None)
-        kwargs['reply_handler'] = lambda ret=None: d.callback(ret)
-        kwargs['error_handler'] = d.errback
+        interface = kwargs.pop("interface", None)
+        kwargs["reply_handler"] = lambda ret=None: d.callback(ret)
+        kwargs["error_handler"] = d.errback
         proxy.get_dbus_method(method, dbus_interface=interface)(*args, **kwargs)
         return d
 
@@ -95,7 +108,9 @@
         @return: True if the method is acceptable
 
         """
-        if method.xpath("arg[@direction='in']"): # we don't accept method with argument for the moment
+        if method.xpath(
+            "arg[@direction='in']"
+        ):  # we don't accept method with argument for the moment
             return False
         return True
 
@@ -104,30 +119,61 @@
         log.debug("introspecting path [%s]" % proxy.object_path)
         introspect_xml = yield self._DBusIntrospect(proxy)
         el = etree.fromstring(introspect_xml)
-        for node in el.iterchildren('node', 'interface'):
-            if node.tag == 'node':
-                new_path = os.path.join(proxy.object_path, node.get('name'))
-                new_proxy = self.session_bus.get_object(bus_name, new_path, introspect=False)
+        for node in el.iterchildren("node", "interface"):
+            if node.tag == "node":
+                new_path = os.path.join(proxy.object_path, node.get("name"))
+                new_proxy = self.session_bus.get_object(
+                    bus_name, new_path, introspect=False
+                )
                 yield self._introspect(methods, bus_name, new_proxy)
-            elif node.tag == 'interface':
-                name = node.get('name')
+            elif node.tag == "interface":
+                name = node.get("name")
                 if any(name.startswith(ignored) for ignored in IGNORED_IFACES_START):
-                    log.debug('interface [%s] is ignored' % name)
+                    log.debug("interface [%s] is ignored" % name)
                     continue
                 log.debug("introspecting interface [%s]" % name)
-                for method in node.iterchildren('method'):
+                for method in node.iterchildren("method"):
                     if self._acceptMethod(method):
-                        method_name = method.get('name')
+                        method_name = method.get("name")
                         log.debug("method accepted: [%s]" % method_name)
                         methods.add((proxy.object_path, name, method_name))
 
-    def _adHocDBusAddAuto(self, prog_name, allowed_jids, allowed_groups, allowed_magics, forbidden_jids, forbidden_groups, flags, profile_key):
-        return self.adHocDBusAddAuto(prog_name, allowed_jids, allowed_groups, allowed_magics, forbidden_jids, forbidden_groups, flags, profile_key)
+    def _adHocDBusAddAuto(
+        self,
+        prog_name,
+        allowed_jids,
+        allowed_groups,
+        allowed_magics,
+        forbidden_jids,
+        forbidden_groups,
+        flags,
+        profile_key,
+    ):
+        return self.adHocDBusAddAuto(
+            prog_name,
+            allowed_jids,
+            allowed_groups,
+            allowed_magics,
+            forbidden_jids,
+            forbidden_groups,
+            flags,
+            profile_key,
+        )
 
     @defer.inlineCallbacks
-    def adHocDBusAddAuto(self, prog_name, allowed_jids=None, allowed_groups=None, allowed_magics=None, forbidden_jids=None, forbidden_groups=None, flags=None, profile_key=C.PROF_KEY_NONE):
+    def adHocDBusAddAuto(
+        self,
+        prog_name,
+        allowed_jids=None,
+        allowed_groups=None,
+        allowed_magics=None,
+        forbidden_jids=None,
+        forbidden_groups=None,
+        flags=None,
+        profile_key=C.PROF_KEY_NONE,
+    ):
         bus_names = yield self._DBusListNames()
-        bus_names = [bus_name for bus_name in bus_names if '.' + prog_name in bus_name]
+        bus_names = [bus_name for bus_name in bus_names if "." + prog_name in bus_name]
         if not bus_names:
             log.info("Can't find any bus for [%s]" % prog_name)
             defer.returnValue(("", []))
@@ -136,45 +182,62 @@
             if bus_name.endswith(prog_name):
                 break
         log.info("bus name found: [%s]" % bus_name)
-        proxy = self.session_bus.get_object(bus_name, '/', introspect=False)
+        proxy = self.session_bus.get_object(bus_name, "/", introspect=False)
         methods = set()
 
         yield self._introspect(methods, bus_name, proxy)
 
         if methods:
-            self._addCommand(prog_name, bus_name, methods,
-                             allowed_jids = allowed_jids,
-                             allowed_groups = allowed_groups,
-                             allowed_magics = allowed_magics,
-                             forbidden_jids = forbidden_jids,
-                             forbidden_groups = forbidden_groups,
-                             flags = flags,
-                             profile_key = profile_key)
+            self._addCommand(
+                prog_name,
+                bus_name,
+                methods,
+                allowed_jids=allowed_jids,
+                allowed_groups=allowed_groups,
+                allowed_magics=allowed_magics,
+                forbidden_jids=forbidden_jids,
+                forbidden_groups=forbidden_groups,
+                flags=flags,
+                profile_key=profile_key,
+            )
 
         defer.returnValue((bus_name, methods))
 
-
-    def _addCommand(self, adhoc_name, bus_name, methods, allowed_jids=None, allowed_groups=None, allowed_magics=None, forbidden_jids=None, forbidden_groups=None, flags=None, profile_key=C.PROF_KEY_NONE):
+    def _addCommand(
+        self,
+        adhoc_name,
+        bus_name,
+        methods,
+        allowed_jids=None,
+        allowed_groups=None,
+        allowed_magics=None,
+        forbidden_jids=None,
+        forbidden_groups=None,
+        flags=None,
+        profile_key=C.PROF_KEY_NONE,
+    ):
         if flags is None:
             flags = set()
 
         def DBusCallback(command_elt, session_data, action, node, profile):
-            actions = session_data.setdefault('actions',[])
-            names_map = session_data.setdefault('names_map', {})
+            actions = session_data.setdefault("actions", [])
+            names_map = session_data.setdefault("names_map", {})
             actions.append(action)
 
             if len(actions) == 1:
                 # it's our first request, we ask the desired new status
                 status = self.XEP_0050.STATUS.EXECUTING
-                form = data_form.Form('form', title=_('Command selection'))
+                form = data_form.Form("form", title=_("Command selection"))
                 options = []
                 for path, iface, command in methods:
-                    label = command.rsplit('.',1)[-1]
+                    label = command.rsplit(".", 1)[-1]
                     name = str(uuid.uuid4())
                     names_map[name] = (path, iface, command)
                     options.append(data_form.Option(name, label))
 
-                field = data_form.Field('list-single', 'command', options=options, required=True)
+                field = data_form.Field(
+                    "list-single", "command", options=options, required=True
+                )
                 form.addField(field)
 
                 payload = form.toElement()
@@ -183,9 +246,9 @@
             elif len(actions) == 2:
                 # we should have the answer here
                 try:
-                    x_elt = command_elt.elements(data_form.NS_X_DATA,'x').next()
+                    x_elt = command_elt.elements(data_form.NS_X_DATA, "x").next()
                     answer_form = data_form.Form.fromElement(x_elt)
-                    command = answer_form['command']
+                    command = answer_form["command"]
                 except (KeyError, StopIteration):
                     raise self.XEP_0050.AdHocError(self.XEP_0050.ERROR.BAD_PAYLOAD)
 
@@ -202,9 +265,11 @@
                     # We have a loop, so we clear everything and we execute again the command as we had a first call (command_elt is not used, so None is OK)
                     del actions[:]
                     names_map.clear()
-                    return DBusCallback(None, session_data, self.XEP_0050.ACTION.EXECUTE, node, profile)
-                form = data_form.Form('form', title=_(u'Updated'))
-                form.addField(data_form.Field('fixed', u'Command sent'))
+                    return DBusCallback(
+                        None, session_data, self.XEP_0050.ACTION.EXECUTE, node, profile
+                    )
+                form = data_form.Form("form", title=_(u"Updated"))
+                form.addField(data_form.Field("fixed", u"Command sent"))
                 status = self.XEP_0050.STATUS.COMPLETED
                 payload = None
                 note = (self.XEP_0050.NOTE.INFO, _(u"Command sent"))
@@ -213,10 +278,13 @@
 
             return (payload, status, None, note)
 
-        self.XEP_0050.addAdHocCommand(DBusCallback, adhoc_name,
-                                      allowed_jids = allowed_jids,
-                                      allowed_groups = allowed_groups,
-                                      allowed_magics = allowed_magics,
-                                      forbidden_jids = forbidden_jids,
-                                      forbidden_groups = forbidden_groups,
-                                      profile_key = profile_key)
+        self.XEP_0050.addAdHocCommand(
+            DBusCallback,
+            adhoc_name,
+            allowed_jids=allowed_jids,
+            allowed_groups=allowed_groups,
+            allowed_magics=allowed_magics,
+            forbidden_jids=forbidden_jids,
+            forbidden_groups=forbidden_groups,
+            profile_key=profile_key,
+        )
--- a/sat/plugins/plugin_blog_import.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_blog_import.py	Wed Jun 27 20:14:46 2018 +0200
@@ -21,6 +21,7 @@
 from sat.core.i18n import _
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from twisted.internet import defer
 from twisted.web import client as web_client
@@ -41,33 +42,36 @@
     C.PI_DEPENDENCIES: ["IMPORT", "XEP-0060", "XEP-0277", "TEXT-SYNTAXES", "UPLOAD"],
     C.PI_MAIN: "BlogImportPlugin",
     C.PI_HANDLER: "no",
-    C.PI_DESCRIPTION: _(u"""Blog import management:
-This plugin manage the different blog importers which can register to it, and handle generic importing tasks.""")
+    C.PI_DESCRIPTION: _(
+        u"""Blog import management:
+This plugin manage the different blog importers which can register to it, and handle generic importing tasks."""
+    ),
 }
 
-OPT_HOST = 'host'
-OPT_UPLOAD_IMAGES = 'upload_images'
-OPT_UPLOAD_IGNORE_HOST = 'upload_ignore_host'
-OPT_IGNORE_TLS = 'ignore_tls_errors'
-URL_REDIRECT_PREFIX = 'url_redirect_'
+OPT_HOST = "host"
+OPT_UPLOAD_IMAGES = "upload_images"
+OPT_UPLOAD_IGNORE_HOST = "upload_ignore_host"
+OPT_IGNORE_TLS = "ignore_tls_errors"
+URL_REDIRECT_PREFIX = "url_redirect_"
 
 
 class BlogImportPlugin(object):
     BOOL_OPTIONS = (OPT_UPLOAD_IMAGES, OPT_IGNORE_TLS)
     JSON_OPTIONS = ()
-    OPT_DEFAULTS = {OPT_UPLOAD_IMAGES: True,
-                    OPT_IGNORE_TLS: False}
+    OPT_DEFAULTS = {OPT_UPLOAD_IMAGES: True, OPT_IGNORE_TLS: False}
 
     def __init__(self, host):
         log.info(_("plugin Blog Import initialization"))
         self.host = host
-        self._u = host.plugins['UPLOAD']
-        self._p = host.plugins['XEP-0060']
-        self._m = host.plugins['XEP-0277']
-        self._s = self.host.plugins['TEXT-SYNTAXES']
-        host.plugins['IMPORT'].initialize(self, u'blog')
+        self._u = host.plugins["UPLOAD"]
+        self._p = host.plugins["XEP-0060"]
+        self._m = host.plugins["XEP-0277"]
+        self._s = self.host.plugins["TEXT-SYNTAXES"]
+        host.plugins["IMPORT"].initialize(self, u"blog")
 
-    def importItem(self, client, item_import_data, session, options, return_data, service, node):
+    def importItem(
+        self, client, item_import_data, session, options, return_data, service, node
+    ):
         """importItem specialized for blog import
 
         @param item_import_data(dict):
@@ -99,51 +103,58 @@
         @param return_data(dict): will contain link between former posts and new items
 
         """
-        mb_data = item_import_data['blog']
+        mb_data = item_import_data["blog"]
         try:
-            item_id = mb_data['id']
+            item_id = mb_data["id"]
         except KeyError:
-            item_id = mb_data['id'] = unicode(shortuuid.uuid())
+            item_id = mb_data["id"] = unicode(shortuuid.uuid())
 
         try:
             # we keep the link between old url and new blog item
             # so the user can redirect its former blog urls
-            old_uri = item_import_data['url']
+            old_uri = item_import_data["url"]
         except KeyError:
             pass
         else:
             new_uri = return_data[URL_REDIRECT_PREFIX + old_uri] = self._p.getNodeURI(
                 service if service is not None else client.jid.userhostJID(),
                 node or self._m.namespace,
-                item_id)
-            log.info(u"url link from {old} to {new}".format(
-                old=old_uri, new=new_uri))
+                item_id,
+            )
+            log.info(u"url link from {old} to {new}".format(old=old_uri, new=new_uri))
 
         return mb_data
 
     @defer.inlineCallbacks
     def importSubItems(self, client, item_import_data, mb_data, session, options):
         # comments data
-        if len(item_import_data['comments']) != 1:
+        if len(item_import_data["comments"]) != 1:
             raise NotImplementedError(u"can't manage multiple comment links")
-        allow_comments = C.bool(mb_data.get('allow_comments', C.BOOL_FALSE))
+        allow_comments = C.bool(mb_data.get("allow_comments", C.BOOL_FALSE))
         if allow_comments:
             comments_service = yield self._m.getCommentsService(client)
-            comments_node = self._m.getCommentsNode(mb_data['id'])
-            mb_data['comments_service'] = comments_service.full()
-            mb_data['comments_node'] = comments_node
+            comments_node = self._m.getCommentsNode(mb_data["id"])
+            mb_data["comments_service"] = comments_service.full()
+            mb_data["comments_node"] = comments_node
             recurse_kwargs = {
-                'items_import_data':item_import_data['comments'][0],
-                'service':comments_service,
-                'node':comments_node}
+                "items_import_data": item_import_data["comments"][0],
+                "service": comments_service,
+                "node": comments_node,
+            }
             defer.returnValue(recurse_kwargs)
         else:
-            if item_import_data['comments'][0]:
-                raise exceptions.DataError(u"allow_comments set to False, but comments are there")
+            if item_import_data["comments"][0]:
+                raise exceptions.DataError(
+                    u"allow_comments set to False, but comments are there"
+                )
             defer.returnValue(None)
 
     def publishItem(self, client, mb_data, service, node, session):
-        log.debug(u"uploading item [{id}]: {title}".format(id=mb_data['id'], title=mb_data.get('title','')))
+        log.debug(
+            u"uploading item [{id}]: {title}".format(
+                id=mb_data["id"], title=mb_data.get("title", "")
+            )
+        )
         return self._m.send(client, mb_data, service, node)
 
     @defer.inlineCallbacks
@@ -161,54 +172,80 @@
             return
 
         # we want only XHTML content
-        for prefix in ('content',): # a tuple is use, if title need to be added in the future
+        for prefix in (
+            "content",
+        ):  # a tuple is use, if title need to be added in the future
             try:
-                rich = mb_data['{}_rich'.format(prefix)]
+                rich = mb_data["{}_rich".format(prefix)]
             except KeyError:
                 pass
             else:
-                if '{}_xhtml'.format(prefix) in mb_data:
-                    raise exceptions.DataError(u"importer gave {prefix}_rich and {prefix}_xhtml at the same time, this is not allowed".format(prefix=prefix))
+                if "{}_xhtml".format(prefix) in mb_data:
+                    raise exceptions.DataError(
+                        u"importer gave {prefix}_rich and {prefix}_xhtml at the same time, this is not allowed".format(
+                            prefix=prefix
+                        )
+                    )
                 # we convert rich syntax to XHTML here, so we can handle filters easily
-                converted = yield self._s.convert(rich, self._s.getCurrentSyntax(client.profile), safe=False)
-                mb_data['{}_xhtml'.format(prefix)] = converted
-                del mb_data['{}_rich'.format(prefix)]
+                converted = yield self._s.convert(
+                    rich, self._s.getCurrentSyntax(client.profile), safe=False
+                )
+                mb_data["{}_xhtml".format(prefix)] = converted
+                del mb_data["{}_rich".format(prefix)]
 
             try:
-                mb_data['txt']
+                mb_data["txt"]
             except KeyError:
                 pass
             else:
-                if '{}_xhtml'.format(prefix) in mb_data:
-                    log.warning(u"{prefix}_text will be replaced by converted {prefix}_xhtml, so filters can be handled".format(prefix=prefix))
-                    del mb_data['{}_text'.format(prefix)]
+                if "{}_xhtml".format(prefix) in mb_data:
+                    log.warning(
+                        u"{prefix}_text will be replaced by converted {prefix}_xhtml, so filters can be handled".format(
+                            prefix=prefix
+                        )
+                    )
+                    del mb_data["{}_text".format(prefix)]
                 else:
-                    log.warning(u"importer gave a text {prefix}, blog filters don't work on text {prefix}".format(prefix=prefix))
+                    log.warning(
+                        u"importer gave a text {prefix}, blog filters don't work on text {prefix}".format(
+                            prefix=prefix
+                        )
+                    )
                     return
 
         # at this point, we have only XHTML version of content
         try:
-            top_elt = xml_tools.ElementParser()(mb_data['content_xhtml'], namespace=C.NS_XHTML)
+            top_elt = xml_tools.ElementParser()(
+                mb_data["content_xhtml"], namespace=C.NS_XHTML
+            )
         except domish.ParserError:
             # we clean the xml and try again our luck
-            cleaned = yield self._s.cleanXHTML(mb_data['content_xhtml'])
+            cleaned = yield self._s.cleanXHTML(mb_data["content_xhtml"])
             top_elt = xml_tools.ElementParser()(cleaned, namespace=C.NS_XHTML)
         opt_host = options.get(OPT_HOST)
         if opt_host:
             # we normalise the domain
             parsed_host = urlparse.urlsplit(opt_host)
-            opt_host = urlparse.urlunsplit((parsed_host.scheme or 'http', parsed_host.netloc or parsed_host.path, '', '', ''))
+            opt_host = urlparse.urlunsplit(
+                (
+                    parsed_host.scheme or "http",
+                    parsed_host.netloc or parsed_host.path,
+                    "",
+                    "",
+                    "",
+                )
+            )
 
         tmp_dir = tempfile.mkdtemp()
         try:
             # TODO: would be nice to also update the hyperlinks to these images, e.g. when you have <a href="{url}"><img src="{url}"></a>
-            for img_elt in xml_tools.findAll(top_elt, names=[u'img']):
+            for img_elt in xml_tools.findAll(top_elt, names=[u"img"]):
                 yield self.imgFilters(client, img_elt, options, opt_host, tmp_dir)
         finally:
-            os.rmdir(tmp_dir) # XXX: tmp_dir should be empty, or something went wrong
+            os.rmdir(tmp_dir)  # XXX: tmp_dir should be empty, or something went wrong
 
         # we now replace the content with filtered one
-        mb_data['content_xhtml'] = top_elt.toXml()
+        mb_data["content_xhtml"] = top_elt.toXml()
 
     @defer.inlineCallbacks
     def imgFilters(self, client, img_elt, options, opt_host, tmp_dir):
@@ -222,15 +259,18 @@
         @param tmp_dir(str): path to temp directory
         """
         try:
-            url = img_elt['src']
-            if url[0] == u'/':
+            url = img_elt["src"]
+            if url[0] == u"/":
                 if not opt_host:
-                    log.warning(u"host was not specified, we can't deal with src without host ({url}) and have to ignore the following <img/>:\n{xml}"
-                        .format(url=url, xml=img_elt.toXml()))
+                    log.warning(
+                        u"host was not specified, we can't deal with src without host ({url}) and have to ignore the following <img/>:\n{xml}".format(
+                            url=url, xml=img_elt.toXml()
+                        )
+                    )
                     return
                 else:
                     url = urlparse.urljoin(opt_host, url)
-            filename = url.rsplit('/',1)[-1].strip()
+            filename = url.rsplit("/", 1)[-1].strip()
             if not filename:
                 raise KeyError
         except (KeyError, IndexError):
@@ -238,7 +278,7 @@
             return
 
         # we change the url for the normalized one
-        img_elt['src'] = url
+        img_elt["src"] = url
 
         if options.get(OPT_UPLOAD_IMAGES, False):
             # upload is requested
@@ -250,23 +290,32 @@
                 # host is the ignored one, we skip
                 parsed_url = urlparse.urlsplit(url)
                 if ignore_host in parsed_url.hostname:
-                    log.info(u"Don't upload image at {url} because of {opt} option".format(
-                        url=url, opt=OPT_UPLOAD_IGNORE_HOST))
+                    log.info(
+                        u"Don't upload image at {url} because of {opt} option".format(
+                            url=url, opt=OPT_UPLOAD_IGNORE_HOST
+                        )
+                    )
                     return
 
             # we download images and re-upload them via XMPP
-            tmp_file = os.path.join(tmp_dir, filename).encode('utf-8')
-            upload_options = {'ignore_tls_errors': options.get(OPT_IGNORE_TLS, False)}
+            tmp_file = os.path.join(tmp_dir, filename).encode("utf-8")
+            upload_options = {"ignore_tls_errors": options.get(OPT_IGNORE_TLS, False)}
 
             try:
-                yield web_client.downloadPage(url.encode('utf-8'), tmp_file)
-                filename = filename.replace(u'%', u'_') # FIXME: tmp workaround for a bug in prosody http upload
-                dummy, download_d = yield self._u.upload(client, tmp_file, filename, options=upload_options)
+                yield web_client.downloadPage(url.encode("utf-8"), tmp_file)
+                filename = filename.replace(
+                    u"%", u"_"
+                )  # FIXME: tmp workaround for a bug in prosody http upload
+                dummy, download_d = yield self._u.upload(
+                    client, tmp_file, filename, options=upload_options
+                )
                 download_url = yield download_d
             except Exception as e:
-                log.warning(u"can't download image at {url}: {reason}".format(url=url, reason=e))
+                log.warning(
+                    u"can't download image at {url}: {reason}".format(url=url, reason=e)
+                )
             else:
-                img_elt['src'] = download_url
+                img_elt["src"] = download_url
 
             try:
                 os.unlink(tmp_file)
--- a/sat/plugins/plugin_blog_import_dokuwiki.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_blog_import_dokuwiki.py	Wed Jun 27 20:14:46 2018 +0200
@@ -21,6 +21,7 @@
 from sat.core.i18n import _, D_
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from sat.core import exceptions
 from sat.tools import xml_tools
@@ -33,14 +34,19 @@
 import re
 import time
 import os.path
+
 try:
     from dokuwiki import DokuWiki, DokuWikiError  # this is a new dependency
 except ImportError:
-    raise exceptions.MissingModule(u'Missing module dokuwiki, please install it with "pip install dokuwiki"')
+    raise exceptions.MissingModule(
+        u'Missing module dokuwiki, please install it with "pip install dokuwiki"'
+    )
 try:
     from PIL import Image  # this is already needed by plugin XEP-0054
 except:
-    raise exceptions.MissingModule(u"Missing module pillow, please download/install it from https://python-pillow.github.io")
+    raise exceptions.MissingModule(
+        u"Missing module pillow, please download/install it from https://python-pillow.github.io"
+    )
 
 PLUGIN_INFO = {
     C.PI_NAME: "Dokuwiki import",
@@ -49,12 +55,13 @@
     C.PI_DEPENDENCIES: ["BLOG_IMPORT"],
     C.PI_MAIN: "DokuwikiImport",
     C.PI_HANDLER: "no",
-    C.PI_DESCRIPTION: _("""Blog importer for Dokuwiki blog engine.""")
+    C.PI_DESCRIPTION: _("""Blog importer for Dokuwiki blog engine."""),
 }
 
 SHORT_DESC = D_(u"import posts from Dokuwiki blog engine")
 
-LONG_DESC = D_(u"""This importer handle Dokuwiki blog engine.
+LONG_DESC = D_(
+    u"""This importer handle Dokuwiki blog engine.
 
 To use it, you need an admin access to a running Dokuwiki website
 (local or on the Internet). The importer retrieves the data using
@@ -91,15 +98,17 @@
 "souliane", and it imports them to sat profile dave's microblog node.
 Internal Dokuwiki media that were hosted on http://127.0.1.1 are now
 pointing to http://media.diekulturvermittlung.at.
-""")
+"""
+)
 DEFAULT_MEDIA_REPO = ""
 DEFAULT_NAMESPACE = "/"
 DEFAULT_LIMIT = 100  # you might get a DBUS timeout (no reply) if it lasts too long
 
 
 class Importer(DokuWiki):
-
-    def __init__(self, url, user, passwd, media_repo=DEFAULT_MEDIA_REPO, limit=DEFAULT_LIMIT):
+    def __init__(
+        self, url, user, passwd, media_repo=DEFAULT_MEDIA_REPO, limit=DEFAULT_LIMIT
+    ):
         """
 
         @param url (unicode): DokuWiki site URL
@@ -120,7 +129,7 @@
         @param post(dict): parsed post data
         @return (unicode): post unique item id
         """
-        return unicode(post['id'])
+        return unicode(post["id"])
 
     def getPostUpdated(self, post):
         """Return the update date.
@@ -128,7 +137,7 @@
         @param post(dict): parsed post data
         @return (unicode): update date
         """
-        return unicode(post['mtime'])
+        return unicode(post["mtime"])
 
     def getPostPublished(self, post):
         """Try to parse the date from the message ID, else use "mtime".
@@ -179,22 +188,23 @@
         # title = content.split("\n")[0].strip(u"\ufeff= ")
 
         # build the extra data dictionary
-        mb_data = {"id": id_,
-                "published": published,
-                "updated": updated,
-                "author": profile_jid.user,
-                # "content": content,  # when passed, it is displayed in Libervia instead of content_xhtml
-                "content_xhtml": content_xhtml,
-                # "title": title,
-                "allow_comments": "true",
-                }
+        mb_data = {
+            "id": id_,
+            "published": published,
+            "updated": updated,
+            "author": profile_jid.user,
+            # "content": content,  # when passed, it is displayed in Libervia instead of content_xhtml
+            "content_xhtml": content_xhtml,
+            # "title": title,
+            "allow_comments": "true",
+        }
 
         # find out if the message access is public or restricted
         namespace = id_.split(":")[0]
         if namespace and namespace.lower() not in ("public", "/"):
             mb_data["group"] = namespace  # roster group must exist
 
-        self.posts_data[id_] = {'blog': mb_data, 'comments':[[]]}
+        self.posts_data[id_] = {"blog": mb_data, "comments": [[]]}
 
     def process(self, client, namespace=DEFAULT_NAMESPACE):
         """Process a namespace or a single page.
@@ -206,7 +216,9 @@
         try:
             pages_list = self.pages.list(namespace)
         except DokuWikiError:
-            log.warning('Could not list Dokuwiki pages: please turn the "display_errors" setting to "Off" in the php.ini of the webserver hosting DokuWiki.')
+            log.warning(
+                'Could not list Dokuwiki pages: please turn the "display_errors" setting to "Off" in the php.ini of the webserver hosting DokuWiki.'
+            )
             return
 
         if not pages_list:  # namespace is actually a page?
@@ -220,7 +232,7 @@
         for page in pages_list:
             self.processPost(page, profile_jid)
             count += 1
-            if count >= self.limit :
+            if count >= self.limit:
                 break
 
         return (self.posts_data.itervalues(), len(self.posts_data))
@@ -334,14 +346,12 @@
             log.error("Cannot create DokuWiki media thumbnail %s" % dest)
 
 
-
 class DokuwikiImport(object):
-
     def __init__(self, host):
         log.info(_("plugin Dokuwiki Import initialization"))
         self.host = host
-        self._blog_import = host.plugins['BLOG_IMPORT']
-        self._blog_import.register('dokuwiki', self.DkImport, SHORT_DESC, LONG_DESC)
+        self._blog_import = host.plugins["BLOG_IMPORT"]
+        self._blog_import.register("dokuwiki", self.DkImport, SHORT_DESC, LONG_DESC)
 
     def DkImport(self, client, location, options=None):
         """Import from DokuWiki to PubSub
@@ -367,14 +377,22 @@
         try:
             media_repo = options["media_repo"]
             if opt_upload_images:
-                options[self._blog_import.OPT_UPLOAD_IMAGES] = False  # force using --no-images-upload
-            info_msg = _("DokuWiki media files will be *downloaded* to {temp_dir} - to finish the import you have to upload them *manually* to {media_repo}")
+                options[
+                    self._blog_import.OPT_UPLOAD_IMAGES
+                ] = False  # force using --no-images-upload
+            info_msg = _(
+                "DokuWiki media files will be *downloaded* to {temp_dir} - to finish the import you have to upload them *manually* to {media_repo}"
+            )
         except KeyError:
             media_repo = DEFAULT_MEDIA_REPO
             if opt_upload_images:
-                info_msg = _("DokuWiki media files will be *uploaded* to the XMPP server. Hyperlinks to these media may not been updated though.")
+                info_msg = _(
+                    "DokuWiki media files will be *uploaded* to the XMPP server. Hyperlinks to these media may not been updated though."
+                )
             else:
-                info_msg = _("DokuWiki media files will *stay* on {location} - some of them may be protected by DokuWiki ACL and will not be accessible.")
+                info_msg = _(
+                    "DokuWiki media files will *stay* on {location} - some of them may be protected by DokuWiki ACL and will not be accessible."
+                )
 
         try:
             namespace = options["namespace"]
@@ -386,7 +404,11 @@
             limit = DEFAULT_LIMIT
 
         dk_importer = Importer(location, user, passwd, media_repo, limit)
-        info_msg = info_msg.format(temp_dir=dk_importer.temp_dir, media_repo=media_repo, location=location)
-        self.host.actionNew({'xmlui': xml_tools.note(info_msg).toXml()}, profile=client.profile)
+        info_msg = info_msg.format(
+            temp_dir=dk_importer.temp_dir, media_repo=media_repo, location=location
+        )
+        self.host.actionNew(
+            {"xmlui": xml_tools.note(info_msg).toXml()}, profile=client.profile
+        )
         d = threads.deferToThread(dk_importer.process, client, namespace)
         return d
--- a/sat/plugins/plugin_blog_import_dotclear.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_blog_import_dotclear.py	Wed Jun 27 20:14:46 2018 +0200
@@ -20,6 +20,7 @@
 from sat.core.i18n import _, D_
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from sat.core import exceptions
 from sat.tools.common import data_format
@@ -38,12 +39,13 @@
     C.PI_DEPENDENCIES: ["BLOG_IMPORT"],
     C.PI_MAIN: "DotclearImport",
     C.PI_HANDLER: "no",
-    C.PI_DESCRIPTION: _("""Blog importer for Dotclear blog engine.""")
+    C.PI_DESCRIPTION: _("""Blog importer for Dotclear blog engine."""),
 }
 
 SHORT_DESC = D_(u"import posts from Dotclear blog engine")
 
-LONG_DESC = D_(u"""This importer handle Dotclear blog engine.
+LONG_DESC = D_(
+    u"""This importer handle Dotclear blog engine.
 
 To use it, you'll need to export your blog to a flat file.
 You must go in your admin interface and select Plugins/Maintenance then Backup.
@@ -51,15 +53,20 @@
 Depending on your configuration, your may need to use Import/Export plugin and export as a flat file.
 
 location: you must use the absolute path to your backup for the location parameter
-""")
+"""
+)
 POST_ID_PREFIX = u"sat_dc_"
-KNOWN_DATA_TYPES = ('link', 'setting', 'post', 'meta', 'media', 'post_media', 'comment', 'captcha')
-ESCAPE_MAP = {
-    'r': u'\r',
-    'n': u'\n',
-    '"': u'"',
-    '\\': u'\\',
-    }
+KNOWN_DATA_TYPES = (
+    "link",
+    "setting",
+    "post",
+    "meta",
+    "media",
+    "post_media",
+    "comment",
+    "captcha",
+)
+ESCAPE_MAP = {"r": u"\r", "n": u"\n", '"': u'"', "\\": u"\\"}
 
 
 class DotclearParser(object):
@@ -76,7 +83,13 @@
         @param post(dict): parsed post data
         @return (unicode): post unique item id
         """
-        return u"{}_{}_{}_{}:{}".format(POST_ID_PREFIX, post['blog_id'], post['user_id'], post['post_id'], post['post_url'])
+        return u"{}_{}_{}_{}:{}".format(
+            POST_ID_PREFIX,
+            post["blog_id"],
+            post["user_id"],
+            post["post_id"],
+            post["post_url"],
+        )
 
     def getCommentId(self, comment):
         """Return a unique and constant comment id
@@ -84,9 +97,9 @@
         @param comment(dict): parsed comment
         @return (unicode): comment unique comment id
         """
-        post_id = comment['post_id']
-        parent_item_id = self.posts_data[post_id]['blog']['id']
-        return u"{}_comment_{}".format(parent_item_id, comment['comment_id'])
+        post_id = comment["post_id"]
+        parent_item_id = self.posts_data[post_id]["blog"]["id"]
+        return u"{}_comment_{}".format(parent_item_id, comment["comment_id"])
 
     def getTime(self, data, key):
         """Parse time as given by dotclear, with timezone handling
@@ -112,18 +125,18 @@
                 if char == '"':
                     # we have reached the end of this field,
                     # we try to parse a new one
-                    yield u''.join(buf)
+                    yield u"".join(buf)
                     buf = []
                     idx += 1
                     try:
                         separator = fields_data[idx]
                     except IndexError:
                         return
-                    if separator != u',':
+                    if separator != u",":
                         raise exceptions.ParsingError("Field separator was expeceted")
                     idx += 1
-                    break # we have a new field
-                elif char == u'\\':
+                    break  # we have a new field
+                elif char == u"\\":
                     idx += 1
                     try:
                         char = ESCAPE_MAP[fields_data[idx]]
@@ -139,55 +152,65 @@
 
     def postHandler(self, headers, data, index):
         post = self.parseFields(headers, data)
-        log.debug(u'({}) post found: {}'.format(index, post['post_title']))
-        mb_data = {'id': self.getPostId(post),
-                   'published': self.getTime(post, 'post_creadt'),
-                   'updated': self.getTime(post, 'post_upddt'),
-                   'author': post['user_id'], # there use info are not in the archive
-                                              # TODO: option to specify user info
-                   'content_xhtml': u"{}{}".format(post['post_content_xhtml'], post['post_excerpt_xhtml']),
-                   'title': post['post_title'],
-                   'allow_comments': C.boolConst(bool(int(post['post_open_comment']))),
-                    }
-        self.posts_data[post['post_id']] = {'blog': mb_data, 'comments':[[]], 'url': u'/post/{}'.format(post['post_url'])}
+        log.debug(u"({}) post found: {}".format(index, post["post_title"]))
+        mb_data = {
+            "id": self.getPostId(post),
+            "published": self.getTime(post, "post_creadt"),
+            "updated": self.getTime(post, "post_upddt"),
+            "author": post["user_id"],  # there use info are not in the archive
+            # TODO: option to specify user info
+            "content_xhtml": u"{}{}".format(
+                post["post_content_xhtml"], post["post_excerpt_xhtml"]
+            ),
+            "title": post["post_title"],
+            "allow_comments": C.boolConst(bool(int(post["post_open_comment"]))),
+        }
+        self.posts_data[post["post_id"]] = {
+            "blog": mb_data,
+            "comments": [[]],
+            "url": u"/post/{}".format(post["post_url"]),
+        }
 
     def metaHandler(self, headers, data, index):
         meta = self.parseFields(headers, data)
-        if meta['meta_type'] == 'tag':
-            tags = self.tags.setdefault(meta['post_id'], set())
-            tags.add(meta['meta_id'])
+        if meta["meta_type"] == "tag":
+            tags = self.tags.setdefault(meta["post_id"], set())
+            tags.add(meta["meta_id"])
 
     def metaFinishedHandler(self):
         for post_id, tags in self.tags.iteritems():
-            data_format.iter2dict('tag', tags, self.posts_data[post_id]['blog'])
+            data_format.iter2dict("tag", tags, self.posts_data[post_id]["blog"])
         del self.tags
 
     def commentHandler(self, headers, data, index):
         comment = self.parseFields(headers, data)
-        if comment['comment_site']:
+        if comment["comment_site"]:
             # we don't use atom:uri because it's used for jid in XMPP
             content = u'{}\n<hr>\n<a href="{}">author website</a>'.format(
-                comment['comment_content'],
-                cgi.escape(comment['comment_site']).replace('"', u'%22'))
+                comment["comment_content"],
+                cgi.escape(comment["comment_site"]).replace('"', u"%22"),
+            )
         else:
-            content = comment['comment_content']
-        mb_data = {'id': self.getCommentId(comment),
-                   'published': self.getTime(comment, 'comment_dt'),
-                   'updated': self.getTime(comment, 'comment_upddt'),
-                   'author': comment['comment_author'],
-                   # we don't keep email addresses to avoid the author to be spammed
-                   # (they would be available publicly else)
-                   # 'author_email': comment['comment_email'],
-                   'content_xhtml': content,
-                  }
-        self.posts_data[comment['post_id']]['comments'][0].append(
-            {'blog': mb_data, 'comments': [[]]})
+            content = comment["comment_content"]
+        mb_data = {
+            "id": self.getCommentId(comment),
+            "published": self.getTime(comment, "comment_dt"),
+            "updated": self.getTime(comment, "comment_upddt"),
+            "author": comment["comment_author"],
+            # we don't keep email addresses to avoid the author to be spammed
+            # (they would be available publicly else)
+            # 'author_email': comment['comment_email'],
+            "content_xhtml": content,
+        }
+        self.posts_data[comment["post_id"]]["comments"][0].append(
+            {"blog": mb_data, "comments": [[]]}
+        )
 
     def parse(self, db_path):
         with open(db_path) as f:
-            signature = f.readline().decode('utf-8')
+            signature = f.readline().decode("utf-8")
             try:
-                version = signature.split('|')[1]
+                version = signature.split("|")[1]
             except IndexError:
                 version = None
             log.debug(u"Dotclear version: {}".format(version))
@@ -195,20 +218,20 @@
             data_headers = None
             index = None
             while True:
-                buf = f.readline().decode('utf-8')
+                buf = f.readline().decode("utf-8")
                 if not buf:
                     break
-                if buf.startswith('['):
-                    header = buf.split(' ', 1)
+                if buf.startswith("["):
+                    header = buf.split(" ", 1)
                     data_type = header[0][1:]
                     if data_type not in KNOWN_DATA_TYPES:
                         log.warning(u"unkown data type: {}".format(data_type))
                     index = 0
                     try:
-                        data_headers = header[1].split(',')
+                        data_headers = header[1].split(",")
                         # we need to remove the ']' from the last header
                         last_header = data_headers[-1]
-                        data_headers[-1] = last_header[:last_header.rfind(']')]
+                        data_headers[-1] = last_header[: last_header.rfind("]")]
                     except IndexError:
                         log.warning(u"Can't read data)")
                 else:
@@ -217,7 +240,9 @@
                     buf = buf.strip()
                     if not buf and data_type in KNOWN_DATA_TYPES:
                         try:
-                            finished_handler = getattr(self, '{}FinishedHandler'.format(data_type))
+                            finished_handler = getattr(
+                                self, "{}FinishedHandler".format(data_type)
+                            )
                         except AttributeError:
                             pass
                         else:
@@ -227,7 +252,7 @@
                         continue
                     assert data_type
                     try:
-                        fields_handler = getattr(self, '{}Handler'.format(data_type))
+                        fields_handler = getattr(self, "{}Handler".format(data_type))
                     except AttributeError:
                         pass
                     else:
@@ -237,15 +262,18 @@
 
 
 class DotclearImport(object):
-
     def __init__(self, host):
         log.info(_("plugin Dotclear Import initialization"))
         self.host = host
-        host.plugins['BLOG_IMPORT'].register('dotclear', self.DcImport, SHORT_DESC, LONG_DESC)
+        host.plugins["BLOG_IMPORT"].register(
+            "dotclear", self.DcImport, SHORT_DESC, LONG_DESC
+        )
 
     def DcImport(self, client, location, options=None):
         if not os.path.isabs(location):
-            raise exceptions.DataError(u"An absolute path to backup data need to be given as location")
+            raise exceptions.DataError(
+                u"An absolute path to backup data need to be given as location"
+            )
         dc_parser = DotclearParser()
         d = threads.deferToThread(dc_parser.parse, location)
         return d
--- a/sat/plugins/plugin_comp_file_sharing.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_comp_file_sharing.py	Wed Jun 27 20:14:46 2018 +0200
@@ -21,6 +21,7 @@
 from sat.core.constants import Const as C
 from sat.core import exceptions
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from sat.tools.common import regex
 from sat.tools.common import uri
@@ -41,29 +42,38 @@
     C.PI_MODES: [C.PLUG_MODE_COMPONENT],
     C.PI_TYPE: C.PLUG_TYPE_ENTRY_POINT,
     C.PI_PROTOCOLS: [],
-    C.PI_DEPENDENCIES: ["FILE", "XEP-0231", "XEP-0234", "XEP-0260", "XEP-0261", "XEP-0264", "XEP-0329"],
+    C.PI_DEPENDENCIES: [
+        "FILE",
+        "XEP-0231",
+        "XEP-0234",
+        "XEP-0260",
+        "XEP-0261",
+        "XEP-0264",
+        "XEP-0329",
+    ],
     C.PI_RECOMMENDATIONS: [],
     C.PI_MAIN: "FileSharing",
     C.PI_HANDLER: C.BOOL_TRUE,
-    C.PI_DESCRIPTION: _(u"""Component hosting and sharing files""")
+    C.PI_DESCRIPTION: _(u"""Component hosting and sharing files"""),
 }
 
-HASH_ALGO = u'sha-256'
-NS_COMMENTS = 'org.salut-a-toi.comments'
-COMMENT_NODE_PREFIX = 'org.salut-a-toi.file_comments/'
+HASH_ALGO = u"sha-256"
+NS_COMMENTS = "org.salut-a-toi.comments"
+COMMENT_NODE_PREFIX = "org.salut-a-toi.file_comments/"
 
 
 class FileSharing(object):
-
     def __init__(self, host):
         log.info(_(u"File Sharing initialization"))
         self.host = host
-        self._f = host.plugins['FILE']
-        self._jf = host.plugins['XEP-0234']
-        self._h = host.plugins['XEP-0300']
-        self._t = host.plugins['XEP-0264']
+        self._f = host.plugins["FILE"]
+        self._jf = host.plugins["XEP-0234"]
+        self._h = host.plugins["XEP-0300"]
+        self._t = host.plugins["XEP-0264"]
         host.trigger.add("FILE_getDestDir", self._getDestDirTrigger)
-        host.trigger.add("XEP-0234_fileSendingRequest", self._fileSendingRequestTrigger, priority=1000)
+        host.trigger.add(
+            "XEP-0234_fileSendingRequest", self._fileSendingRequestTrigger, priority=1000
+        )
         host.trigger.add("XEP-0234_buildFileElement", self._addFileComments)
         host.trigger.add("XEP-0234_parseFileElement", self._getFileComments)
         host.trigger.add("XEP-0329_compGetFilesFromNode", self._addCommentsData)
@@ -74,9 +84,10 @@
 
     def profileConnected(self, client):
         path = client.file_tmp_dir = os.path.join(
-            self.host.memory.getConfig('', 'local_dir'),
+            self.host.memory.getConfig("", "local_dir"),
             C.FILES_TMP_DIR,
-            regex.pathEscape(client.profile))
+            regex.pathEscape(client.profile),
+        )
         if not os.path.exists(path):
             os.makedirs(path)
 
@@ -87,155 +98,195 @@
         on file is received, this method create hash/thumbnails if necessary
         move the file to the right location, and create metadata entry in database
         """
-        name = file_data[u'name']
+        name = file_data[u"name"]
         extra = {}
 
-        if file_data[u'hash_algo'] == HASH_ALGO:
+        if file_data[u"hash_algo"] == HASH_ALGO:
             log.debug(_(u"Reusing already generated hash"))
-            file_hash = file_data[u'hash_hasher'].hexdigest()
+            file_hash = file_data[u"hash_hasher"].hexdigest()
         else:
             hasher = self._h.getHasher(HASH_ALGO)
-            with open('file_path') as f:
+            with open("file_path") as f:
                 file_hash = yield self._h.calculateHash(f, hasher)
         final_path = os.path.join(self.files_path, file_hash)
 
         if os.path.isfile(final_path):
-            log.debug(u"file [{file_hash}] already exists, we can remove temporary one".format(file_hash = file_hash))
+            log.debug(
+                u"file [{file_hash}] already exists, we can remove temporary one".format(
+                    file_hash=file_hash
+                )
+            )
             os.unlink(file_path)
         else:
             os.rename(file_path, final_path)
-            log.debug(u"file [{file_hash}] moved to {files_path}".format(file_hash=file_hash, files_path=self.files_path))
+            log.debug(
+                u"file [{file_hash}] moved to {files_path}".format(
+                    file_hash=file_hash, files_path=self.files_path
+                )
+            )
 
-        mime_type = file_data.get(u'mime_type')
-        if not mime_type or mime_type == u'application/octet-stream':
+        mime_type = file_data.get(u"mime_type")
+        if not mime_type or mime_type == u"application/octet-stream":
             mime_type = mimetypes.guess_type(name)[0]
 
-        if mime_type is not None and mime_type.startswith(u'image'):
+        if mime_type is not None and mime_type.startswith(u"image"):
             thumbnails = extra.setdefault(C.KEY_THUMBNAILS, [])
             for max_thumb_size in (self._t.SIZE_SMALL, self._t.SIZE_MEDIUM):
                 try:
-                    thumb_size, thumb_id = yield self._t.generateThumbnail(final_path,
-                                                                           max_thumb_size,
-                                                                           # we keep thumbnails for 6 months
-                                                                           60*60*24*31*6)
+                    thumb_size, thumb_id = yield self._t.generateThumbnail(
+                        final_path,
+                        max_thumb_size,
+                        #  we keep thumbnails for 6 months
+                        60 * 60 * 24 * 31 * 6,
+                    )
                 except Exception as e:
                     log.warning(_(u"Can't create thumbnail: {reason}").format(reason=e))
                     break
-                thumbnails.append({u'id': thumb_id, u'size': thumb_size})
+                thumbnails.append({u"id": thumb_id, u"size": thumb_size})
 
-        self.host.memory.setFile(client,
-                                 name=name,
-                                 version=u'',
-                                 file_hash=file_hash,
-                                 hash_algo=HASH_ALGO,
-                                 size=file_data[u'size'],
-                                 path=file_data.get(u'path'),
-                                 namespace=file_data.get(u'namespace'),
-                                 mime_type=mime_type,
-                                 owner=peer_jid,
-                                 extra=extra)
+        self.host.memory.setFile(
+            client,
+            name=name,
+            version=u"",
+            file_hash=file_hash,
+            hash_algo=HASH_ALGO,
+            size=file_data[u"size"],
+            path=file_data.get(u"path"),
+            namespace=file_data.get(u"namespace"),
+            mime_type=mime_type,
+            owner=peer_jid,
+            extra=extra,
+        )
 
-    def _getDestDirTrigger(self, client, peer_jid, transfer_data, file_data, stream_object):
+    def _getDestDirTrigger(
+        self, client, peer_jid, transfer_data, file_data, stream_object
+    ):
         """This trigger accept file sending request, and store file locally"""
         if not client.is_component:
             return True, None
         assert stream_object
-        assert 'stream_object' not in transfer_data
+        assert "stream_object" not in transfer_data
         assert C.KEY_PROGRESS_ID in file_data
-        filename = file_data['name']
-        assert filename and not '/' in filename
-        file_tmp_dir = self.host.getLocalPath(client, C.FILES_TMP_DIR, peer_jid.userhost(), component=True, profile=False)
-        file_tmp_path = file_data['file_path'] = os.path.join(file_tmp_dir, file_data['name'])
+        filename = file_data["name"]
+        assert filename and not "/" in filename
+        file_tmp_dir = self.host.getLocalPath(
+            client, C.FILES_TMP_DIR, peer_jid.userhost(), component=True, profile=False
+        )
+        file_tmp_path = file_data["file_path"] = os.path.join(
+            file_tmp_dir, file_data["name"]
+        )
 
-        transfer_data['finished_d'].addCallback(self._fileTransferedCb, client, peer_jid, file_data, file_tmp_path)
+        transfer_data["finished_d"].addCallback(
+            self._fileTransferedCb, client, peer_jid, file_data, file_tmp_path
+        )
 
-        self._f.openFileWrite(client, file_tmp_path, transfer_data, file_data, stream_object)
+        self._f.openFileWrite(
+            client, file_tmp_path, transfer_data, file_data, stream_object
+        )
         return False, defer.succeed(True)
 
     @defer.inlineCallbacks
-    def _retrieveFiles(self, client, session, content_data, content_name, file_data, file_elt):
+    def _retrieveFiles(
+        self, client, session, content_data, content_name, file_data, file_elt
+    ):
         """This method retrieve a file on request, and send if after checking permissions"""
-        peer_jid = session[u'peer_jid']
+        peer_jid = session[u"peer_jid"]
         try:
-            found_files = yield self.host.memory.getFiles(client,
-                                                          peer_jid=peer_jid,
-                                                          name=file_data.get(u'name'),
-                                                          file_hash=file_data.get(u'file_hash'),
-                                                          hash_algo=file_data.get(u'hash_algo'),
-                                                          path=file_data.get(u'path'),
-                                                          namespace=file_data.get(u'namespace'))
+            found_files = yield self.host.memory.getFiles(
+                client,
+                peer_jid=peer_jid,
+                name=file_data.get(u"name"),
+                file_hash=file_data.get(u"file_hash"),
+                hash_algo=file_data.get(u"hash_algo"),
+                path=file_data.get(u"path"),
+                namespace=file_data.get(u"namespace"),
+            )
         except exceptions.NotFound:
             found_files = None
         except exceptions.PermissionError:
-            log.warning(_(u"{peer_jid} is trying to access an unauthorized file: {name}").format(
-                peer_jid=peer_jid, name=file_data.get(u'name')))
+            log.warning(
+                _(u"{peer_jid} is trying to access an unauthorized file: {name}").format(
+                    peer_jid=peer_jid, name=file_data.get(u"name")
+                )
+            )
             defer.returnValue(False)
 
         if not found_files:
-            log.warning(_(u"no matching file found ({file_data})").format(file_data=file_data))
+            log.warning(
+                _(u"no matching file found ({file_data})").format(file_data=file_data)
+            )
             defer.returnValue(False)
 
         # we only use the first found file
         found_file = found_files[0]
-        file_hash = found_file[u'file_hash']
+        file_hash = found_file[u"file_hash"]
         file_path = os.path.join(self.files_path, file_hash)
-        file_data[u'hash_hasher'] = hasher = self._h.getHasher(found_file[u'hash_algo'])
-        size = file_data[u'size'] = found_file[u'size']
-        file_data[u'file_hash'] = file_hash
-        file_data[u'hash_algo'] = found_file[u'hash_algo']
+        file_data[u"hash_hasher"] = hasher = self._h.getHasher(found_file[u"hash_algo"])
+        size = file_data[u"size"] = found_file[u"size"]
+        file_data[u"file_hash"] = file_hash
+        file_data[u"hash_algo"] = found_file[u"hash_algo"]
 
         # we complete file_elt so peer can have some details on the file
-        if u'name' not in file_data:
-            file_elt.addElement(u'name', content=found_file[u'name'])
-        file_elt.addElement(u'size', content=unicode(size))
-        content_data['stream_object'] = stream.FileStreamObject(
+        if u"name" not in file_data:
+            file_elt.addElement(u"name", content=found_file[u"name"])
+        file_elt.addElement(u"size", content=unicode(size))
+        content_data["stream_object"] = stream.FileStreamObject(
             self.host,
             client,
             file_path,
             uid=self._jf.getProgressId(session, content_name),
             size=size,
             data_cb=lambda data: hasher.update(data),
-            )
+        )
         defer.returnValue(True)
 
-    def _fileSendingRequestTrigger(self, client, session, content_data, content_name, file_data, file_elt):
+    def _fileSendingRequestTrigger(
+        self, client, session, content_data, content_name, file_data, file_elt
+    ):
         if not client.is_component:
             return True, None
         else:
-            return False, self._retrieveFiles(client, session, content_data, content_name, file_data, file_elt)
+            return (
+                False,
+                self._retrieveFiles(
+                    client, session, content_data, content_name, file_data, file_elt
+                ),
+            )
 
     ## comments triggers ##
 
     def _addFileComments(self, file_elt, extra_args):
         try:
-            comments_url = extra_args.pop('comments_url')
+            comments_url = extra_args.pop("comments_url")
         except KeyError:
             return
 
-        comment_elt = file_elt.addElement((NS_COMMENTS, 'comments'), content=comments_url)
+        comment_elt = file_elt.addElement((NS_COMMENTS, "comments"), content=comments_url)
 
         try:
-            count = len(extra_args[u'extra'][u'comments'])
+            count = len(extra_args[u"extra"][u"comments"])
         except KeyError:
             count = 0
 
-        comment_elt['count'] = unicode(count)
+        comment_elt["count"] = unicode(count)
         return True
 
     def _getFileComments(self, file_elt, file_data):
         try:
-            comments_elt = next(file_elt.elements(NS_COMMENTS, 'comments'))
+            comments_elt = next(file_elt.elements(NS_COMMENTS, "comments"))
         except StopIteration:
             return
-        file_data['comments_url'] = unicode(comments_elt)
-        file_data['comments_count'] = comments_elt['count']
+        file_data["comments_url"] = unicode(comments_elt)
+        file_data["comments_count"] = comments_elt["count"]
         return True
 
     def _addCommentsData(self, client, iq_elt, owner, node_path, files_data):
         for file_data in files_data:
-            file_data['comments_url'] = uri.buildXMPPUri('pubsub',
-                                                         path=client.jid.full(),
-                                                         node=COMMENT_NODE_PREFIX + file_data['id'])
+            file_data["comments_url"] = uri.buildXMPPUri(
+                "pubsub",
+                path=client.jid.full(),
+                node=COMMENT_NODE_PREFIX + file_data["id"],
+            )
         return True
 
 
@@ -243,19 +294,21 @@
     """This class is a minimal Pubsub service handling virtual nodes for comments"""
 
     def __init__(self, plugin_parent):
-        super(Comments_handler, self).__init__() # PubsubVirtualResource())
+        super(Comments_handler, self).__init__()  # PubsubVirtualResource())
         self.host = plugin_parent.host
         self.plugin_parent = plugin_parent
-        self.discoIdentity = {'category': 'pubsub',
-                              'type': 'virtual',  # FIXME: non standard, here to avoid this service being considered as main pubsub one
-                              'name': 'files commenting service'}
+        self.discoIdentity = {
+            "category": "pubsub",
+            "type": "virtual",  # FIXME: non standard, here to avoid this service being considered as main pubsub one
+            "name": "files commenting service",
+        }
 
     def _getFileId(self, nodeIdentifier):
         if not nodeIdentifier.startswith(COMMENT_NODE_PREFIX):
-            raise error.StanzaError('item-not-found')
-        file_id = nodeIdentifier[len(COMMENT_NODE_PREFIX):]
+            raise error.StanzaError("item-not-found")
+        file_id = nodeIdentifier[len(COMMENT_NODE_PREFIX) :]
         if not file_id:
-            raise error.StanzaError('item-not-found')
+            raise error.StanzaError("item-not-found")
         return file_id
 
     @defer.inlineCallbacks
@@ -266,11 +319,11 @@
         except (exceptions.NotFound, exceptions.PermissionError):
             # we don't differenciate between NotFound and PermissionError
             # to avoid leaking information on existing files
-            raise error.StanzaError('item-not-found')
+            raise error.StanzaError("item-not-found")
         if not files:
-            raise error.StanzaError('item-not-found')
+            raise error.StanzaError("item-not-found")
         if len(files) > 1:
-            raise error.InternalError('there should be only one file')
+            raise error.InternalError("there should be only one file")
         defer.returnValue(files[0])
 
     def commentsUpdate(self, extra, new_comments, peer_jid):
@@ -280,8 +333,8 @@
         @param new_comments(list[tuple(unicode, unicode, unicode)]): comments to update or insert
         @param peer_jid(unicode, None): bare jid of the requestor, or None if request is done by owner
         """
-        current_comments = extra.setdefault('comments', [])
-        new_comments_by_id = {c[0]:c for c in new_comments}
+        current_comments = extra.setdefault("comments", [])
+        new_comments_by_id = {c[0]: c for c in new_comments}
         updated = []
         # we now check every current comment, to see if one id in new ones
         # exist, in which case we must update
@@ -306,7 +359,7 @@
 
     def commentsDelete(self, extra, comments):
         try:
-            comments_dict = extra['comments']
+            comments_dict = extra["comments"]
         except KeyError:
             return
         for comment in comments:
@@ -324,42 +377,43 @@
         iq_elt = item_elt
         while iq_elt.parent != None:
             iq_elt = iq_elt.parent
-        return iq_elt['from']
+        return iq_elt["from"]
 
     @defer.inlineCallbacks
     def publish(self, requestor, service, nodeIdentifier, items):
-        # we retrieve file a first time to check authorisations
+        #  we retrieve file a first time to check authorisations
         file_data = yield self.getFileData(requestor, nodeIdentifier)
-        file_id = file_data['id']
-        comments = [(item['id'], self._getFrom(item), item.toXml()) for item in items]
-        if requestor.userhostJID() == file_data['owner']:
+        file_id = file_data["id"]
+        comments = [(item["id"], self._getFrom(item), item.toXml()) for item in items]
+        if requestor.userhostJID() == file_data["owner"]:
             peer_jid = None
         else:
             peer_jid = requestor.userhost()
         update_cb = partial(self.commentsUpdate, new_comments=comments, peer_jid=peer_jid)
         try:
-            yield self.host.memory.fileUpdate(file_id, 'extra', update_cb)
+            yield self.host.memory.fileUpdate(file_id, "extra", update_cb)
         except exceptions.PermissionError:
-            raise error.StanzaError('not-authorized')
+            raise error.StanzaError("not-authorized")
 
     @defer.inlineCallbacks
-    def items(self, requestor, service, nodeIdentifier, maxItems,
-                    itemIdentifiers):
+    def items(self, requestor, service, nodeIdentifier, maxItems, itemIdentifiers):
         file_data = yield self.getFileData(requestor, nodeIdentifier)
-        comments = file_data['extra'].get('comments', [])
+        comments = file_data["extra"].get("comments", [])
         if itemIdentifiers:
-            defer.returnValue([generic.parseXml(c[2]) for c in comments if c[0] in itemIdentifiers])
+            defer.returnValue(
+                [generic.parseXml(c[2]) for c in comments if c[0] in itemIdentifiers]
+            )
         else:
             defer.returnValue([generic.parseXml(c[2]) for c in comments])
 
     @defer.inlineCallbacks
     def retract(self, requestor, service, nodeIdentifier, itemIdentifiers):
         file_data = yield self.getFileData(requestor, nodeIdentifier)
-        file_id = file_data['id']
+        file_id = file_data["id"]
         try:
-            comments = file_data['extra']['comments']
+            comments = file_data["extra"]["comments"]
         except KeyError:
-            raise error.StanzaError('item-not-found')
+            raise error.StanzaError("item-not-found")
 
         to_remove = []
         for comment in comments:
@@ -372,11 +426,11 @@
 
         if itemIdentifiers:
             # not all items have been to_remove, we can't continue
-            raise error.StanzaError('item-not-found')
+            raise error.StanzaError("item-not-found")
 
-        if requestor.userhostJID() != file_data['owner']:
+        if requestor.userhostJID() != file_data["owner"]:
             if not all([c[1] == requestor.userhost() for c in to_remove]):
-                raise error.StanzaError('not-authorized')
+                raise error.StanzaError("not-authorized")
 
         remove_cb = partial(self.commentsDelete, comments=to_remove)
-        yield self.host.memory.fileUpdate(file_id, 'extra', remove_cb)
+        yield self.host.memory.fileUpdate(file_id, "extra", remove_cb)
--- a/sat/plugins/plugin_exp_command_export.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_exp_command_export.py	Wed Jun 27 20:14:46 2018 +0200
@@ -20,6 +20,7 @@
 from sat.core.i18n import _
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from twisted.words.protocols.jabber import jid
 from twisted.internet import reactor, protocol
@@ -35,9 +36,10 @@
     C.PI_DEPENDENCIES: [],
     C.PI_MAIN: "CommandExport",
     C.PI_HANDLER: "no",
-    C.PI_DESCRIPTION: _("""Implementation of command export""")
+    C.PI_DESCRIPTION: _("""Implementation of command export"""),
 }
 
+
 class ExportCommandProtocol(protocol.ProcessProtocol):
     """ Try to register an account with prosody """
 
@@ -49,26 +51,26 @@
 
     def _clean(self, data):
         if not data:
-            log.error ("data should not be empty !")
+            log.error("data should not be empty !")
             return u""
-        decoded = data.decode('utf-8', 'ignore')[:-1 if data[-1] == '\n' else None]
+        decoded = data.decode("utf-8", "ignore")[: -1 if data[-1] == "\n" else None]
         return clean_ustr(decoded)
 
     def connectionMade(self):
         log.info("connectionMade :)")
 
     def outReceived(self, data):
-        self.client.sendMessage(self.target, {'': self._clean(data)}, no_trigger=True)
+        self.client.sendMessage(self.target, {"": self._clean(data)}, no_trigger=True)
 
     def errReceived(self, data):
-        self.client.sendMessage(self.target, {'': self._clean(data)}, no_trigger=True)
+        self.client.sendMessage(self.target, {"": self._clean(data)}, no_trigger=True)
 
     def processEnded(self, reason):
-        log.info (u"process finished: %d" % (reason.value.exitCode,))
+        log.info(u"process finished: %d" % (reason.value.exitCode,))
         self.parent.removeProcess(self.target, self)
 
     def write(self, message):
-        self.transport.write(message.encode('utf-8'))
+        self.transport.write(message.encode("utf-8"))
 
     def boolOption(self, key):
         """ Get boolean value from options
@@ -81,6 +83,7 @@
 
 class CommandExport(object):
     """Command export plugin: export a command to an entity"""
+
     # XXX: This plugin can be potentially dangerous if we don't trust entities linked
     #      this is specially true if we have other triggers.
     # FIXME: spawned should be a client attribute, not a class one
@@ -88,9 +91,15 @@
     def __init__(self, host):
         log.info(_("Plugin command export initialization"))
         self.host = host
-        self.spawned = {} # key = entity
+        self.spawned = {}  # key = entity
         host.trigger.add("MessageReceived", self.MessageReceivedTrigger, priority=10000)
-        host.bridge.addMethod("exportCommand", ".plugin", in_sign='sasasa{ss}s', out_sign='', method=self._exportCommand)
+        host.bridge.addMethod(
+            "exportCommand",
+            ".plugin",
+            in_sign="sasasa{ss}s",
+            out_sign="",
+            method=self._exportCommand,
+        )
 
     def removeProcess(self, entity, process):
         """ Called when the process is finished
@@ -100,7 +109,7 @@
             processes_set = self.spawned[(entity, process.client.profile)]
             processes_set.discard(process)
             if not processes_set:
-                del(self.spawned[(entity, process.client.profile)])
+                del (self.spawned[(entity, process.client.profile)])
         except ValueError:
             pass
 
@@ -111,12 +120,12 @@
 
         if spawned_key in self.spawned:
             try:
-                body = message_elt.elements(C.NS_CLIENT, 'body').next()
+                body = message_elt.elements(C.NS_CLIENT, "body").next()
             except StopIteration:
                 # do not block message without body (chat state notification...)
                 return True
 
-            mess_data = unicode(body) + '\n'
+            mess_data = unicode(body) + "\n"
             processes_set = self.spawned[spawned_key]
             _continue = False
             exclusive = False
@@ -152,5 +161,7 @@
                 log.info(u"invalid target ignored: %s" % (target,))
                 continue
             process_prot = ExportCommandProtocol(self, client, _jid, options)
-            self.spawned.setdefault((_jid, client.profile),set()).add(process_prot)
-            reactor.spawnProcess(process_prot, command, args, usePTY = process_prot.boolOption('pty'))
+            self.spawned.setdefault((_jid, client.profile), set()).add(process_prot)
+            reactor.spawnProcess(
+                process_prot, command, args, usePTY=process_prot.boolOption("pty")
+            )
--- a/sat/plugins/plugin_exp_events.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_exp_events.py	Wed Jun 27 20:14:46 2018 +0200
@@ -21,6 +21,7 @@
 from sat.core import exceptions
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from sat.tools import utils
 from sat.tools.common import uri as xmpp_uri
@@ -45,14 +46,15 @@
     C.PI_RECOMMENDATIONS: ["INVITATIONS", "XEP-0277"],
     C.PI_MAIN: "Events",
     C.PI_HANDLER: "yes",
-    C.PI_DESCRIPTION: _("""Experimental implementation of XMPP events management""")
+    C.PI_DESCRIPTION: _("""Experimental implementation of XMPP events management"""),
 }
 
-NS_EVENT = 'org.salut-a-toi.event:0'
-NS_EVENT_LIST = NS_EVENT + '#list'
-NS_EVENT_INVIT = NS_EVENT + '#invitation'
+NS_EVENT = "org.salut-a-toi.event:0"
+NS_EVENT_LIST = NS_EVENT + "#list"
+NS_EVENT_INVIT = NS_EVENT + "#invitation"
 INVITATION = '/message[@type="chat"]/invitation[@xmlns="{ns_invit}"]'.format(
-    ns_invit=NS_EVENT_INVIT)
+    ns_invit=NS_EVENT_INVIT
+)
 
 
 class Events(object):
@@ -64,40 +66,78 @@
         self._p = self.host.plugins["XEP-0060"]
         self._i = self.host.plugins.get("INVITATIONS")
         self._b = self.host.plugins.get("XEP-0277")
-        host.bridge.addMethod("eventGet", ".plugin",
-                              in_sign='ssss', out_sign='(ia{ss})',
-                              method=self._eventGet,
-                              async=True)
-        host.bridge.addMethod("eventCreate", ".plugin",
-                              in_sign='ia{ss}ssss', out_sign='s',
-                              method=self._eventCreate,
-                              async=True)
-        host.bridge.addMethod("eventModify", ".plugin",
-                              in_sign='sssia{ss}s', out_sign='',
-                              method=self._eventModify,
-                              async=True)
-        host.bridge.addMethod("eventsList", ".plugin",
-                              in_sign='sss', out_sign='aa{ss}',
-                              method=self._eventsList,
-                              async=True)
-        host.bridge.addMethod("eventInviteeGet", ".plugin",
-                              in_sign='sss', out_sign='a{ss}',
-                              method=self._eventInviteeGet,
-                              async=True)
-        host.bridge.addMethod("eventInviteeSet", ".plugin",
-                              in_sign='ssa{ss}s', out_sign='',
-                              method=self._eventInviteeSet,
-                              async=True)
-        host.bridge.addMethod("eventInviteesList", ".plugin",
-                              in_sign='sss', out_sign='a{sa{ss}}',
-                              method=self._eventInviteesList,
-                              async=True),
-        host.bridge.addMethod("eventInvite", ".plugin", in_sign='sssss', out_sign='',
-                              method=self._invite,
-                              async=True)
-        host.bridge.addMethod("eventInviteByEmail", ".plugin", in_sign='ssssassssssss', out_sign='',
-                              method=self._inviteByEmail,
-                              async=True)
+        host.bridge.addMethod(
+            "eventGet",
+            ".plugin",
+            in_sign="ssss",
+            out_sign="(ia{ss})",
+            method=self._eventGet,
+            async=True,
+        )
+        host.bridge.addMethod(
+            "eventCreate",
+            ".plugin",
+            in_sign="ia{ss}ssss",
+            out_sign="s",
+            method=self._eventCreate,
+            async=True,
+        )
+        host.bridge.addMethod(
+            "eventModify",
+            ".plugin",
+            in_sign="sssia{ss}s",
+            out_sign="",
+            method=self._eventModify,
+            async=True,
+        )
+        host.bridge.addMethod(
+            "eventsList",
+            ".plugin",
+            in_sign="sss",
+            out_sign="aa{ss}",
+            method=self._eventsList,
+            async=True,
+        )
+        host.bridge.addMethod(
+            "eventInviteeGet",
+            ".plugin",
+            in_sign="sss",
+            out_sign="a{ss}",
+            method=self._eventInviteeGet,
+            async=True,
+        )
+        host.bridge.addMethod(
+            "eventInviteeSet",
+            ".plugin",
+            in_sign="ssa{ss}s",
+            out_sign="",
+            method=self._eventInviteeSet,
+            async=True,
+        )
+        host.bridge.addMethod(
+            "eventInviteesList",
+            ".plugin",
+            in_sign="sss",
+            out_sign="a{sa{ss}}",
+            method=self._eventInviteesList,
+            async=True,
+        ),
+        host.bridge.addMethod(
+            "eventInvite",
+            ".plugin",
+            in_sign="sssss",
+            out_sign="",
+            method=self._invite,
+            async=True,
+        )
+        host.bridge.addMethod(
+            "eventInviteByEmail",
+            ".plugin",
+            in_sign="ssssassssssss",
+            out_sign="",
+            method=self._inviteByEmail,
+            async=True,
+        )
 
     def getHandler(self, client):
         return EventsHandler(self)
@@ -115,13 +155,13 @@
 
         data = {}
 
-        for key in (u'name',):
+        for key in (u"name",):
             try:
                 data[key] = event_elt[key]
             except KeyError:
                 continue
 
-        for elt_name in (u'description',):
+        for elt_name in (u"description",):
             try:
                 elt = next(event_elt.elements(NS_EVENT, elt_name))
             except StopIteration:
@@ -129,21 +169,21 @@
             else:
                 data[elt_name] = unicode(elt)
 
-        for elt_name in (u'image', 'background-image'):
+        for elt_name in (u"image", "background-image"):
             try:
                 image_elt = next(event_elt.elements(NS_EVENT, elt_name))
-                data[elt_name] = image_elt['src']
+                data[elt_name] = image_elt["src"]
             except StopIteration:
                 continue
             except KeyError:
-                log.warning(_(u'no src found for image'))
+                log.warning(_(u"no src found for image"))
 
-        for uri_type in (u'invitees', u'blog'):
+        for uri_type in (u"invitees", u"blog"):
             try:
                 elt = next(event_elt.elements(NS_EVENT, uri_type))
-                uri = data[uri_type + u'_uri'] = elt['uri']
+                uri = data[uri_type + u"_uri"] = elt["uri"]
                 uri_data = xmpp_uri.parseXMPPUri(uri)
-                if uri_data[u'type'] != u'pubsub':
+                if uri_data[u"type"] != u"pubsub":
                     raise ValueError
             except StopIteration:
                 log.warning(_(u"no {uri_type} element found!").format(uri_type=uri_type))
@@ -152,22 +192,26 @@
             except ValueError:
                 log.warning(_(u"bad {uri_type} element").format(uri_type=uri_type))
             else:
-                data[uri_type + u'_service'] = uri_data[u'path']
-                data[uri_type + u'_node'] = uri_data[u'node']
+                data[uri_type + u"_service"] = uri_data[u"path"]
+                data[uri_type + u"_node"] = uri_data[u"node"]
 
-        for meta_elt in event_elt.elements(NS_EVENT, 'meta'):
-            key = meta_elt[u'name']
+        for meta_elt in event_elt.elements(NS_EVENT, "meta"):
+            key = meta_elt[u"name"]
             if key in data:
-                log.warning(u'Ignoring conflicting meta element: {xml}'.format(xml=meta_elt.toXml()))
+                log.warning(
+                    u"Ignoring conflicting meta element: {xml}".format(
+                        xml=meta_elt.toXml()
+                    )
+                )
                 continue
             data[key] = unicode(meta_elt)
         if event_elt.link:
             link_elt = event_elt.link
-            data['service'] = link_elt['service']
-            data['node'] = link_elt['node']
-            data['item'] = link_elt['item']
-        if event_elt.getAttribute('creator') == 'true':
-            data['creator'] = True
+            data["service"] = link_elt["service"]
+            data["node"] = link_elt["node"]
+            data["item"] = link_elt["item"]
+        if event_elt.getAttribute("creator") == "true":
+            data["creator"] = True
         return timestamp, data
 
     @defer.inlineCallbacks
@@ -184,7 +228,7 @@
             id_ = NS_EVENT
         items, metadata = yield self._p.getItems(client, service, node, item_ids=[id_])
         try:
-            event_elt = next(items[0].elements(NS_EVENT, u'event'))
+            event_elt = next(items[0].elements(NS_EVENT, u"event"))
         except IndexError:
             raise exceptions.NotFound(_(u"No event with this id has been found"))
         defer.returnValue(event_elt)
@@ -204,30 +248,30 @@
         try:
             # TODO: check auto-create, no need to create node first if available
             options = {self._p.OPT_ACCESS_MODEL: self._p.ACCESS_WHITELIST}
-            yield self._p.createNode(client,
-                                     client.jid.userhostJID(),
-                                     nodeIdentifier=NS_EVENT_LIST,
-                                     options=options)
+            yield self._p.createNode(
+                client,
+                client.jid.userhostJID(),
+                nodeIdentifier=NS_EVENT_LIST,
+                options=options,
+            )
         except error.StanzaError as e:
-            if e.condition == u'conflict':
+            if e.condition == u"conflict":
                 log.debug(_(u"requested node already exists"))
-        link_elt = event_elt.addElement((NS_EVENT_LIST, 'link'))
+        link_elt = event_elt.addElement((NS_EVENT_LIST, "link"))
         link_elt["service"] = service.full()
         link_elt["node"] = node
         link_elt["item"] = event_id
-        item_id = xmpp_uri.buildXMPPUri(u'pubsub',
-                                        path=service.full(),
-                                        node=node,
-                                        item=event_id)
+        item_id = xmpp_uri.buildXMPPUri(
+            u"pubsub", path=service.full(), node=node, item=event_id
+        )
         if creator:
-            event_elt['creator'] = 'true'
+            event_elt["creator"] = "true"
         item_elt = pubsub.Item(id=item_id, payload=event_elt)
-        yield self._p.publish(client,
-                              client.jid.userhostJID(),
-                              NS_EVENT_LIST,
-                              items=[item_elt])
+        yield self._p.publish(
+            client, client.jid.userhostJID(), NS_EVENT_LIST, items=[item_elt]
+        )
 
-    def _eventGet(self, service, node, id_=u'', profile_key=C.PROF_KEY_NONE):
+    def _eventGet(self, service, node, id_=u"", profile_key=C.PROF_KEY_NONE):
         service = jid.JID(service) if service else None
         node = node if node else NS_EVENT
         client = self.host.getClient(profile_key)
@@ -251,11 +295,13 @@
 
         defer.returnValue(self._parseEventElt(event_elt))
 
-    def _eventCreate(self, timestamp, data, service, node, id_=u'', profile_key=C.PROF_KEY_NONE):
+    def _eventCreate(
+        self, timestamp, data, service, node, id_=u"", profile_key=C.PROF_KEY_NONE
+    ):
         service = jid.JID(service) if service else None
         node = node or None
         client = self.host.getClient(profile_key)
-        data[u'register'] = C.bool(data.get(u'register', C.BOOL_FALSE))
+        data[u"register"] = C.bool(data.get(u"register", C.BOOL_FALSE))
         return self.eventCreate(client, timestamp, data, service, node, id_ or NS_EVENT)
 
     @defer.inlineCallbacks
@@ -282,57 +328,66 @@
         if not service:
             service = client.jid.userhostJID()
         if not node:
-            node = NS_EVENT + u'__' + shortuuid.uuid()
-        event_elt = domish.Element((NS_EVENT, 'event'))
+            node = NS_EVENT + u"__" + shortuuid.uuid()
+        event_elt = domish.Element((NS_EVENT, "event"))
         if timestamp is not None and timestamp != -1:
             formatted_date = utils.xmpp_date(timestamp)
-            event_elt.addElement((NS_EVENT, 'date'), content=formatted_date)
-        register = data.pop('register', False)
-        for key in (u'name',):
+            event_elt.addElement((NS_EVENT, "date"), content=formatted_date)
+        register = data.pop("register", False)
+        for key in (u"name",):
             if key in data:
                 event_elt[key] = data.pop(key)
-        for key in (u'description',):
+        for key in (u"description",):
             if key in data:
                 event_elt.addElement((NS_EVENT, key), content=data.pop(key))
-        for key in (u'image', u'background-image'):
+        for key in (u"image", u"background-image"):
             if key in data:
                 elt = event_elt.addElement((NS_EVENT, key))
-                elt['src'] = data.pop(key)
+                elt["src"] = data.pop(key)
 
         # we first create the invitees and blog nodes (if not specified in data)
-        for uri_type in (u'invitees', u'blog'):
-            key = uri_type + u'_uri'
-            for to_delete in (u'service', u'node'):
-                k = uri_type + u'_' + to_delete
+        for uri_type in (u"invitees", u"blog"):
+            key = uri_type + u"_uri"
+            for to_delete in (u"service", u"node"):
+                k = uri_type + u"_" + to_delete
                 if k in data:
                     del data[k]
             if key not in data:
                 # FIXME: affiliate invitees
                 uri_node = yield self._p.createNode(client, service)
-                yield self._p.setConfiguration(client, service, uri_node, {self._p.OPT_ACCESS_MODEL: self._p.ACCESS_WHITELIST})
+                yield self._p.setConfiguration(
+                    client,
+                    service,
+                    uri_node,
+                    {self._p.OPT_ACCESS_MODEL: self._p.ACCESS_WHITELIST},
+                )
                 uri_service = service
             else:
                 uri = data.pop(key)
                 uri_data = xmpp_uri.parseXMPPUri(uri)
-                if uri_data[u'type'] != u'pubsub':
-                    raise ValueError(_(u'The given URI is not valid: {uri}').format(uri=uri))
-                uri_service = jid.JID(uri_data[u'path'])
-                uri_node = uri_data[u'node']
+                if uri_data[u"type"] != u"pubsub":
+                    raise ValueError(
+                        _(u"The given URI is not valid: {uri}").format(uri=uri)
+                    )
+                uri_service = jid.JID(uri_data[u"path"])
+                uri_node = uri_data[u"node"]
 
             elt = event_elt.addElement((NS_EVENT, uri_type))
-            elt['uri'] = xmpp_uri.buildXMPPUri('pubsub', path=uri_service.full(), node=uri_node)
+            elt["uri"] = xmpp_uri.buildXMPPUri(
+                "pubsub", path=uri_service.full(), node=uri_node
+            )
 
         # remaining data are put in <meta> elements
         for key in data.keys():
-            elt = event_elt.addElement((NS_EVENT, 'meta'), content = data.pop(key))
-            elt['name'] = key
+            elt = event_elt.addElement((NS_EVENT, "meta"), content=data.pop(key))
+            elt["name"] = key
 
         item_elt = pubsub.Item(id=event_id, payload=event_elt)
         try:
             # TODO: check auto-create, no need to create node first if available
             node = yield self._p.createNode(client, service, nodeIdentifier=node)
         except error.StanzaError as e:
-            if e.condition == u'conflict':
+            if e.condition == u"conflict":
                 log.debug(_(u"requested node already exists"))
 
         yield self._p.publish(client, service, node, items=[item_elt])
@@ -341,14 +396,26 @@
             yield self.register(client, service, node, event_id, event_elt, creator=True)
         defer.returnValue(node)
 
-    def _eventModify(self, service, node, id_, timestamp_update, data_update, profile_key=C.PROF_KEY_NONE):
+    def _eventModify(
+        self,
+        service,
+        node,
+        id_,
+        timestamp_update,
+        data_update,
+        profile_key=C.PROF_KEY_NONE,
+    ):
         service = jid.JID(service) if service else None
         node = node if node else NS_EVENT
         client = self.host.getClient(profile_key)
-        return self.eventModify(client, service, node, id_ or NS_EVENT, timestamp_update or None, data_update)
+        return self.eventModify(
+            client, service, node, id_ or NS_EVENT, timestamp_update or None, data_update
+        )
 
     @defer.inlineCallbacks
-    def eventModify(self, client, service, node, id_=NS_EVENT, timestamp_update=None, data_update=None):
+    def eventModify(
+        self, client, service, node, id_=NS_EVENT, timestamp_update=None, data_update=None
+    ):
         """Update an event
 
         Similar as create instead that it update existing item instead of
@@ -364,8 +431,8 @@
 
     def _eventsListSerialise(self, events):
         for timestamp, data in events:
-            data['date'] = unicode(timestamp)
-            data['creator'] = C.boolConst(data.get('creator', False))
+            data["date"] = unicode(timestamp)
+            data["creator"] = C.boolConst(data.get("creator", False))
         return [e[1] for e in events]
 
     def _eventsList(self, service, node, profile):
@@ -388,10 +455,11 @@
         events = []
         for item in items[0]:
             try:
-                event_elt = next(item.elements(NS_EVENT, u'event'))
+                event_elt = next(item.elements(NS_EVENT, u"event"))
             except IndexError:
-                log.error(_(u"No event found in item {item_id}").format(
-                    item_id = item['id']))
+                log.error(
+                    _(u"No event found in item {item_id}").format(item_id=item["id"])
+                )
             timestamp, data = self._parseEventElt(event_elt)
             events.append((timestamp, data))
         defer.returnValue(events)
@@ -411,21 +479,23 @@
         @return (dict): a dict with current attendance status,
             an empty dict is returned if nothing has been answered yed
         """
-        items, metadata = yield self._p.getItems(client, service, node, item_ids=[client.jid.userhost()])
+        items, metadata = yield self._p.getItems(
+            client, service, node, item_ids=[client.jid.userhost()]
+        )
         try:
-            event_elt = next(items[0].elements(NS_EVENT, u'invitee'))
+            event_elt = next(items[0].elements(NS_EVENT, u"invitee"))
         except IndexError:
             # no item found, event data are not set yet
             defer.returnValue({})
         data = {}
-        for key in (u'attend', u'guests'):
+        for key in (u"attend", u"guests"):
             try:
                 data[key] = event_elt[key]
             except KeyError:
                 continue
         defer.returnValue(data)
 
-    def _eventInviteeSet(self, service, node, event_data,  profile_key):
+    def _eventInviteeSet(self, service, node, event_data, profile_key):
         service = jid.JID(service) if service else None
         node = node if node else NS_EVENT
         client = self.host.getClient(profile_key)
@@ -441,8 +511,8 @@
                 attend: one of "yes", "no", "maybe"
                 guests: an int
         """
-        event_elt = domish.Element((NS_EVENT, 'invitee'))
-        for key in (u'attend', u'guests'):
+        event_elt = domish.Element((NS_EVENT, "invitee"))
+        for key in (u"attend", u"guests"):
             try:
                 event_elt[key] = data.pop(key)
             except KeyError:
@@ -469,22 +539,24 @@
         invitees = {}
         for item in items:
             try:
-                event_elt = next(item.elements(NS_EVENT, u'invitee'))
+                event_elt = next(item.elements(NS_EVENT, u"invitee"))
             except StopIteration:
                 # no item found, event data are not set yet
-                log.warning(_(u"no data found for {item_id} (service: {service}, node: {node})".format(
-                    item_id=item['id'],
-                    service=service,
-                    node=node
-                    )))
+                log.warning(
+                    _(
+                        u"no data found for {item_id} (service: {service}, node: {node})".format(
+                            item_id=item["id"], service=service, node=node
+                        )
+                    )
+                )
             else:
                 data = {}
-                for key in (u'attend', u'guests'):
+                for key in (u"attend", u"guests"):
                     try:
                         data[key] = event_elt[key]
                     except KeyError:
                         continue
-                invitees[item['id']] = data
+                invitees[item["id"]] = data
         defer.returnValue(invitees)
 
     def sendMessageInvitation(self, client, invitee_jid, service, node, item_id):
@@ -496,20 +568,20 @@
         @param item_id(unicode): id of the event
         """
         mess_data = {
-            'from': client.jid,
-            'to': invitee_jid,
-            'uid': '',
-            'message': {},
-            'type': C.MESS_TYPE_CHAT,
-            'subject': {},
-            'extra': {},
-            }
+            "from": client.jid,
+            "to": invitee_jid,
+            "uid": "",
+            "message": {},
+            "type": C.MESS_TYPE_CHAT,
+            "subject": {},
+            "extra": {},
+        }
         client.generateMessageXML(mess_data)
-        event_elt = mess_data['xml'].addElement('invitation', NS_EVENT_INVIT)
-        event_elt['service'] = service.full()
-        event_elt['node'] = node
-        event_elt['item'] = item_id
-        client.send(mess_data['xml'])
+        event_elt = mess_data["xml"].addElement("invitation", NS_EVENT_INVIT)
+        event_elt["service"] = service.full()
+        event_elt["node"] = node
+        event_elt["item"] = item_id
+        client.send(mess_data["xml"])
 
     def _invite(self, invitee_jid, service, node, item_id, profile):
         client = self.host.getClient(profile)
@@ -531,51 +603,83 @@
         @param item_id(unicode): event id
         """
         if self._b is None:
-            raise exceptions.FeatureNotFound(_(u'"XEP-0277" (blog) plugin is needed for this feature'))
+            raise exceptions.FeatureNotFound(
+                _(u'"XEP-0277" (blog) plugin is needed for this feature')
+            )
         if item_id is None:
             item_id = NS_EVENT
 
         # first we authorize our invitee to see the nodes of interest
-        yield self._p.setNodeAffiliations(client, service, node, {invitee_jid: u'member'})
-        log.debug(_(u'affiliation set on event node'))
+        yield self._p.setNodeAffiliations(client, service, node, {invitee_jid: u"member"})
+        log.debug(_(u"affiliation set on event node"))
         dummy, event_data = yield self.eventGet(client, service, node, item_id)
-        log.debug(_(u'got event data'))
-        invitees_service = jid.JID(event_data['invitees_service'])
-        invitees_node = event_data['invitees_node']
-        blog_service = jid.JID(event_data['blog_service'])
-        blog_node = event_data['blog_node']
-        yield self._p.setNodeAffiliations(client, invitees_service, invitees_node, {invitee_jid: u'publisher'})
-        log.debug(_(u'affiliation set on invitee node'))
-        yield self._p.setNodeAffiliations(client, blog_service, blog_node, {invitee_jid: u'member'})
+        log.debug(_(u"got event data"))
+        invitees_service = jid.JID(event_data["invitees_service"])
+        invitees_node = event_data["invitees_node"]
+        blog_service = jid.JID(event_data["blog_service"])
+        blog_node = event_data["blog_node"]
+        yield self._p.setNodeAffiliations(
+            client, invitees_service, invitees_node, {invitee_jid: u"publisher"}
+        )
+        log.debug(_(u"affiliation set on invitee node"))
+        yield self._p.setNodeAffiliations(
+            client, blog_service, blog_node, {invitee_jid: u"member"}
+        )
         blog_items, dummy = yield self._b.mbGet(client, blog_service, blog_node, None)
 
         for item in blog_items:
             try:
-                comments_service = jid.JID(item['comments_service'])
-                comments_node = item['comments_node']
+                comments_service = jid.JID(item["comments_service"])
+                comments_node = item["comments_node"]
             except KeyError:
-                log.debug(u"no comment service set for item {item_id}".format(item_id=item['id']))
+                log.debug(
+                    u"no comment service set for item {item_id}".format(
+                        item_id=item["id"]
+                    )
+                )
             else:
-                yield self._p.setNodeAffiliations(client, comments_service, comments_node, {invitee_jid: u'publisher'})
-        log.debug(_(u'affiliation set on blog and comments nodes'))
+                yield self._p.setNodeAffiliations(
+                    client, comments_service, comments_node, {invitee_jid: u"publisher"}
+                )
+        log.debug(_(u"affiliation set on blog and comments nodes"))
 
         # now we send the invitation
         self.sendMessageInvitation(client, invitee_jid, service, node, item_id)
 
-    def _inviteByEmail(self, service, node, id_=NS_EVENT, email=u'', emails_extra=None, name=u'', host_name=u'', language=u'', url_template=u'',
-        message_subject=u'', message_body=u'', profile_key=C.PROF_KEY_NONE):
+    def _inviteByEmail(
+        self,
+        service,
+        node,
+        id_=NS_EVENT,
+        email=u"",
+        emails_extra=None,
+        name=u"",
+        host_name=u"",
+        language=u"",
+        url_template=u"",
+        message_subject=u"",
+        message_body=u"",
+        profile_key=C.PROF_KEY_NONE,
+    ):
         client = self.host.getClient(profile_key)
-        kwargs = {u'profile': client.profile,
-                  u'emails_extra': [unicode(e) for e in emails_extra]
-                 }
-        for key in ("email", "name", "host_name", "language", "url_template", "message_subject", "message_body"):
+        kwargs = {
+            u"profile": client.profile,
+            u"emails_extra": [unicode(e) for e in emails_extra],
+        }
+        for key in (
+            "email",
+            "name",
+            "host_name",
+            "language",
+            "url_template",
+            "message_subject",
+            "message_body",
+        ):
             value = locals()[key]
             kwargs[key] = unicode(value)
-        return self.inviteByEmail(client,
-                                  jid.JID(service) if service else None,
-                                  node,
-                                  id_ or NS_EVENT,
-                                  **kwargs)
+        return self.inviteByEmail(
+            client, jid.JID(service) if service else None, node, id_ or NS_EVENT, **kwargs
+        )
 
     @defer.inlineCallbacks
     def inviteByEmail(self, client, service, node, id_=NS_EVENT, **kwargs):
@@ -586,18 +690,21 @@
         @param id_(unicode): id_ with even data
         """
         if self._i is None:
-            raise exceptions.FeatureNotFound(_(u'"Invitations" plugin is needed for this feature'))
+            raise exceptions.FeatureNotFound(
+                _(u'"Invitations" plugin is needed for this feature')
+            )
         if self._b is None:
-            raise exceptions.FeatureNotFound(_(u'"XEP-0277" (blog) plugin is needed for this feature'))
+            raise exceptions.FeatureNotFound(
+                _(u'"XEP-0277" (blog) plugin is needed for this feature')
+            )
         service = service or client.jid.userhostJID()
-        event_uri = xmpp_uri.buildXMPPUri('pubsub',
-            path=service.full(),
-            node=node,
-            item=id_)
-        kwargs['extra'] = {u'event_uri': event_uri}
+        event_uri = xmpp_uri.buildXMPPUri(
+            "pubsub", path=service.full(), node=node, item=id_
+        )
+        kwargs["extra"] = {u"event_uri": event_uri}
         invitation_data = yield self._i.create(**kwargs)
-        invitee_jid = invitation_data[u'jid']
-        log.debug(_(u'invitation created'))
+        invitee_jid = invitation_data[u"jid"]
+        log.debug(_(u"invitation created"))
         # now that we have a jid, we can send normal invitation
         yield self.invite(client, invitee_jid, service, node, id_)
 
@@ -605,9 +712,9 @@
     def onInvitation(self, message_elt, client):
         invitation_elt = message_elt.invitation
         try:
-            service = jid.JID(invitation_elt['service'])
-            node = invitation_elt['node']
-            event_id = invitation_elt['item']
+            service = jid.JID(invitation_elt["service"])
+            node = invitation_elt["node"]
+            event_id = invitation_elt["item"]
         except (RuntimeError, KeyError):
             log.warning(_(u"Bad invitation: {xml}").format(xml=message_elt.toXml()))
 
@@ -626,14 +733,16 @@
         return self.plugin_parent.host
 
     def connectionInitialized(self):
-        self.xmlstream.addObserver(INVITATION,
-            self.plugin_parent.onInvitation,
-            client=self.parent)
+        self.xmlstream.addObserver(
+            INVITATION, self.plugin_parent.onInvitation, client=self.parent
+        )
 
-    def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
-        return [disco.DiscoFeature(NS_EVENT),
-                disco.DiscoFeature(NS_EVENT_LIST),
-                disco.DiscoFeature(NS_EVENT_INVIT)]
+    def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
+        return [
+            disco.DiscoFeature(NS_EVENT),
+            disco.DiscoFeature(NS_EVENT_LIST),
+            disco.DiscoFeature(NS_EVENT_INVIT),
+        ]
 
-    def getDiscoItems(self, requestor, target, nodeIdentifier=''):
+    def getDiscoItems(self, requestor, target, nodeIdentifier=""):
         return []
--- a/sat/plugins/plugin_exp_jingle_stream.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_exp_jingle_stream.py	Wed Jun 27 20:14:46 2018 +0200
@@ -21,6 +21,7 @@
 from sat.core.constants import Const as C
 from sat.core import exceptions
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from sat.tools import xml_tools
 from sat.tools import stream
@@ -35,8 +36,8 @@
 from zope import interface
 import errno
 
-NS_STREAM = 'http://salut-a-toi.org/protocol/stream'
-SECURITY_LIMIT=30
+NS_STREAM = "http://salut-a-toi.org/protocol/stream"
+SECURITY_LIMIT = 30
 START_PORT = 8888
 
 PLUGIN_INFO = {
@@ -47,7 +48,7 @@
     C.PI_DEPENDENCIES: ["XEP-0166"],
     C.PI_MAIN: "JingleStream",
     C.PI_HANDLER: "no",
-    C.PI_DESCRIPTION: _("""Jingle Stream plugin""")
+    C.PI_DESCRIPTION: _("""Jingle Stream plugin"""),
 }
 
 CONFIRM = D_(u"{peer} wants to send you a stream, do you accept ?")
@@ -55,7 +56,6 @@
 
 
 class StreamProtocol(protocol.Protocol):
-
     def __init__(self):
         self.pause = False
 
@@ -118,7 +118,9 @@
 
     def startStream(self, consumer):
         if self.consumer is not None:
-            raise exceptions.InternalError(_(u"stream can't be used with multiple consumers"))
+            raise exceptions.InternalError(
+                _(u"stream can't be used with multiple consumers")
+            )
         assert self.deferred is None
         self.consumer = consumer
         consumer.registerProducer(self, True)
@@ -176,9 +178,17 @@
     def __init__(self, host):
         log.info(_("Plugin Stream initialization"))
         self.host = host
-        self._j = host.plugins["XEP-0166"] # shortcut to access jingle
+        self._j = host.plugins["XEP-0166"]  # shortcut to access jingle
         self._j.registerApplication(NS_STREAM, self)
-        host.bridge.addMethod("streamOut", ".plugin", in_sign='ss', out_sign='s', method=self._streamOut, async=True)
+        host.bridge.addMethod(
+            "streamOut",
+            ".plugin",
+            in_sign="ss",
+            out_sign="s",
+            method=self._streamOut,
+            async=True,
+        )
+
     # jingle callbacks
 
     def _streamOut(self, to_jid_s, profile_key):
@@ -206,64 +216,75 @@
             else:
                 factory.port_listening = port_listening
                 break
-        self._j.initiate(client,
-                         to_jid,
-                         [{'app_ns': NS_STREAM,
-                           'senders': self._j.ROLE_INITIATOR,
-                           'app_kwargs': {'stream_object': factory},
-                         }])
+        self._j.initiate(
+            client,
+            to_jid,
+            [
+                {
+                    "app_ns": NS_STREAM,
+                    "senders": self._j.ROLE_INITIATOR,
+                    "app_kwargs": {"stream_object": factory},
+                }
+            ],
+        )
         defer.returnValue(unicode(port))
 
     def jingleSessionInit(self, client, session, content_name, stream_object):
-        content_data = session['contents'][content_name]
-        application_data = content_data['application_data']
-        assert 'stream_object' not in application_data
-        application_data['stream_object'] = stream_object
-        desc_elt = domish.Element((NS_STREAM, 'description'))
+        content_data = session["contents"][content_name]
+        application_data = content_data["application_data"]
+        assert "stream_object" not in application_data
+        application_data["stream_object"] = stream_object
+        desc_elt = domish.Element((NS_STREAM, "description"))
         return desc_elt
 
     @defer.inlineCallbacks
     def jingleRequestConfirmation(self, client, action, session, content_name, desc_elt):
         """This method request confirmation for a jingle session"""
-        content_data = session['contents'][content_name]
-        if content_data['senders'] not in (self._j.ROLE_INITIATOR, self._j.ROLE_RESPONDER):
+        content_data = session["contents"][content_name]
+        if content_data["senders"] not in (
+            self._j.ROLE_INITIATOR,
+            self._j.ROLE_RESPONDER,
+        ):
             log.warning(u"Bad sender, assuming initiator")
-            content_data['senders'] = self._j.ROLE_INITIATOR
+            content_data["senders"] = self._j.ROLE_INITIATOR
 
-        confirm_data = yield xml_tools.deferDialog(self.host,
-            _(CONFIRM).format(peer=session['peer_jid'].full()),
+        confirm_data = yield xml_tools.deferDialog(
+            self.host,
+            _(CONFIRM).format(peer=session["peer_jid"].full()),
             _(CONFIRM_TITLE),
             type_=C.XMLUI_DIALOG_CONFIRM,
-            action_extra={'meta_from_jid': session['peer_jid'].full(),
-                          'meta_type': "STREAM",
-                         },
+            action_extra={
+                "meta_from_jid": session["peer_jid"].full(),
+                "meta_type": "STREAM",
+            },
             security_limit=SECURITY_LIMIT,
-            profile=client.profile)
+            profile=client.profile,
+        )
 
-        if not C.bool(confirm_data['answer']):
+        if not C.bool(confirm_data["answer"]):
             defer.returnValue(False)
         try:
-            port = int(confirm_data['port'])
+            port = int(confirm_data["port"])
         except (ValueError, KeyError):
-            raise exceptions.DataError(_(u'given port is invalid'))
-        endpoint = endpoints.TCP4ClientEndpoint(reactor, 'localhost', port)
+            raise exceptions.DataError(_(u"given port is invalid"))
+        endpoint = endpoints.TCP4ClientEndpoint(reactor, "localhost", port)
         factory = StreamFactory()
         yield endpoint.connect(factory)
-        content_data['stream_object'] = factory
-        finished_d = content_data['finished_d'] = defer.Deferred()
+        content_data["stream_object"] = factory
+        finished_d = content_data["finished_d"] = defer.Deferred()
         args = [client, session, content_name, content_data]
         finished_d.addCallbacks(self._finishedCb, self._finishedEb, args, None, args)
         defer.returnValue(True)
 
     def jingleHandler(self, client, action, session, content_name, desc_elt):
-        content_data = session['contents'][content_name]
-        application_data = content_data['application_data']
+        content_data = session["contents"][content_name]
+        application_data = content_data["application_data"]
         if action in (self._j.A_ACCEPTED_ACK, self._j.A_SESSION_INITIATE):
             pass
         elif action == self._j.A_SESSION_ACCEPT:
-            assert not 'stream_object' in content_data
-            content_data['stream_object'] = application_data['stream_object']
-            finished_d = content_data['finished_d'] = defer.Deferred()
+            assert not "stream_object" in content_data
+            content_data["stream_object"] = application_data["stream_object"]
+            finished_d = content_data["finished_d"] = defer.Deferred()
             args = [client, session, content_name, content_data]
             finished_d.addCallbacks(self._finishedCb, self._finishedEb, args, None, args)
         else:
@@ -273,9 +294,11 @@
     def _finishedCb(self, dummy, client, session, content_name, content_data):
         log.info(u"Pipe transfer completed")
         self._j.contentTerminate(client, session, content_name)
-        content_data['stream_object'].stopStream()
+        content_data["stream_object"].stopStream()
 
     def _finishedEb(self, failure, client, session, content_name, content_data):
         log.warning(u"Error while streaming pipe: {}".format(failure))
-        self._j.contentTerminate(client, session, content_name, reason=self._j.REASON_FAILED_TRANSPORT)
-        content_data['stream_object'].stopStream()
+        self._j.contentTerminate(
+            client, session, content_name, reason=self._j.REASON_FAILED_TRANSPORT
+        )
+        content_data["stream_object"].stopStream()
--- a/sat/plugins/plugin_exp_lang_detect.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_exp_lang_detect.py	Wed Jun 27 20:14:46 2018 +0200
@@ -20,13 +20,16 @@
 from sat.core.i18n import _, D_
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from sat.core import exceptions
 
 try:
     from langid.langid import LanguageIdentifier, model
 except ImportError:
-    raise exceptions.MissingModule(u'Missing module langid, please download/install it with "pip install langid")')
+    raise exceptions.MissingModule(
+        u'Missing module langid, please download/install it with "pip install langid")'
+    )
 
 identifier = LanguageIdentifier.from_modelstring(model, norm_probs=False)
 
@@ -39,7 +42,7 @@
     C.PI_DEPENDENCIES: [],
     C.PI_MAIN: "LangDetect",
     C.PI_HANDLER: "no",
-    C.PI_DESCRIPTION: _("""Detect and set message language when unknown""")
+    C.PI_DESCRIPTION: _("""Detect and set message language when unknown"""),
 }
 
 CATEGORY = D_(u"Misc")
@@ -53,14 +56,12 @@
     </category>
     </individual>
     </params>
-    """.format(category_name=CATEGORY,
-               name=NAME,
-               label=_(LABEL),
-              )
+    """.format(
+    category_name=CATEGORY, name=NAME, label=_(LABEL)
+)
 
 
 class LangDetect(object):
-
     def __init__(self, host):
         log.info(_(u"Language detection plugin initialization"))
         self.host = host
@@ -69,8 +70,8 @@
         host.trigger.add("sendMessage", self.MessageSendTrigger)
 
     def addLanguage(self, mess_data):
-        message = mess_data['message']
-        if len(message) == 1 and message.keys()[0] == '':
+        message = mess_data["message"]
+        if len(message) == 1 and message.keys()[0] == "":
             msg = message.values()[0]
             lang = identifier.classify(msg)[0]
             mess_data["message"] = {lang: msg}
@@ -79,13 +80,17 @@
     def MessageReceivedTrigger(self, client, message_elt, post_treat):
         """ Check if source is linked and repeat message, else do nothing  """
 
-        lang_detect = self.host.memory.getParamA(NAME, CATEGORY, profile_key=client.profile)
+        lang_detect = self.host.memory.getParamA(
+            NAME, CATEGORY, profile_key=client.profile
+        )
         if lang_detect:
             post_treat.addCallback(self.addLanguage)
         return True
 
     def MessageSendTrigger(self, client, data, pre_xml_treatments, post_xml_treatments):
-        lang_detect = self.host.memory.getParamA(NAME, CATEGORY, profile_key=client.profile)
+        lang_detect = self.host.memory.getParamA(
+            NAME, CATEGORY, profile_key=client.profile
+        )
         if lang_detect:
             self.addLanguage(data)
         return True
--- a/sat/plugins/plugin_exp_parrot.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_exp_parrot.py	Wed Jun 27 20:14:46 2018 +0200
@@ -20,11 +20,13 @@
 from sat.core.i18n import _
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from twisted.words.protocols.jabber import jid
 
 from sat.core.exceptions import UnknownEntityError
-#from sat.tools import trigger
+
+# from sat.tools import trigger
 
 PLUGIN_INFO = {
     C.PI_NAME: "Parrot Plugin",
@@ -35,12 +37,15 @@
     C.PI_RECOMMENDATIONS: [C.TEXT_CMDS],
     C.PI_MAIN: "Exp_Parrot",
     C.PI_HANDLER: "no",
-    C.PI_DESCRIPTION: _(u"""Implementation of parrot mode (repeat messages between 2 entities)""")
+    C.PI_DESCRIPTION: _(
+        u"""Implementation of parrot mode (repeat messages between 2 entities)"""
+    ),
 }
 
 
 class Exp_Parrot(object):
     """Parrot mode plugin: repeat messages from one entity or MUC room to another one"""
+
     # XXX: This plugin can be potentially dangerous if we don't trust entities linked
     #      this is specially true if we have other triggers.
     #      sendMessageTrigger avoid other triggers execution, it's deactivated to allow
@@ -51,13 +56,13 @@
         log.info(_("Plugin Parrot initialization"))
         self.host = host
         host.trigger.add("MessageReceived", self.MessageReceivedTrigger, priority=100)
-        #host.trigger.add("sendMessage", self.sendMessageTrigger, priority=100)
+        # host.trigger.add("sendMessage", self.sendMessageTrigger, priority=100)
         try:
             self.host.plugins[C.TEXT_CMDS].registerTextCommands(self)
         except KeyError:
             log.info(_(u"Text commands not available"))
 
-    #def sendMessageTrigger(self, client, mess_data, treatments):
+    # def sendMessageTrigger(self, client, mess_data, treatments):
     #    """ Deactivate other triggers if recipient is in parrot links """
     #    try:
     #        _links = client.parrot_links
@@ -84,18 +89,22 @@
             return True
 
         message = {}
-        for e in message_elt.elements(C.NS_CLIENT, 'body'):
+        for e in message_elt.elements(C.NS_CLIENT, "body"):
             body = unicode(e)
-            lang = e.getAttribute('lang') or ''
+            lang = e.getAttribute("lang") or ""
 
             try:
-                entity_type = self.host.memory.getEntityData(from_jid, ['type'], profile)["type"]
+                entity_type = self.host.memory.getEntityData(from_jid, ["type"], profile)[
+                    "type"
+                ]
             except (UnknownEntityError, KeyError):
                 entity_type = "contact"
-            if entity_type == 'chatroom':
+            if entity_type == "chatroom":
                 src_txt = from_jid.resource
-                if src_txt == self.host.plugins["XEP-0045"].getRoomNick(client, from_jid.userhostJID()):
-                    #we won't repeat our own messages
+                if src_txt == self.host.plugins["XEP-0045"].getRoomNick(
+                    client, from_jid.userhostJID()
+                ):
+                    # we won't repeat our own messages
                     return True
             else:
                 src_txt = from_jid.user
@@ -103,7 +112,9 @@
 
             linked = _links[from_jid.userhostJID()]
 
-            client.sendMessage(jid.JID(unicode(linked)), message, None, "auto", no_trigger=True)
+            client.sendMessage(
+                jid.JID(unicode(linked)), message, None, "auto", no_trigger=True
+            )
 
         return True
 
@@ -119,7 +130,10 @@
             _links = client.parrot_links = {}
 
         _links[source_jid.userhostJID()] = dest_jid
-        log.info(u"Parrot mode: %s will be repeated to %s" % (source_jid.userhost(), unicode(dest_jid)))
+        log.info(
+            u"Parrot mode: %s will be repeated to %s"
+            % (source_jid.userhost(), unicode(dest_jid))
+        )
 
     def removeParrot(self, client, source_jid):
         """Remove parrot link
@@ -141,15 +155,21 @@
             if not link_left_jid.user or not link_left_jid.host:
                 raise jid.InvalidFormat
         except (RuntimeError, jid.InvalidFormat, AttributeError):
-            txt_cmd.feedBack(client, "Can't activate Parrot mode for invalid jid", mess_data)
+            txt_cmd.feedBack(
+                client, "Can't activate Parrot mode for invalid jid", mess_data
+            )
             return False
 
-        link_right_jid = mess_data['to']
+        link_right_jid = mess_data["to"]
 
         self.addParrot(client, link_left_jid, link_right_jid)
         self.addParrot(client, link_right_jid, link_left_jid)
 
-        txt_cmd.feedBack(client, "Parrot mode activated for {}".format(unicode(link_left_jid)), mess_data)
+        txt_cmd.feedBack(
+            client,
+            "Parrot mode activated for {}".format(unicode(link_left_jid)),
+            mess_data,
+        )
 
         return False
 
@@ -163,14 +183,22 @@
             if not link_left_jid.user or not link_left_jid.host:
                 raise jid.InvalidFormat
         except jid.InvalidFormat:
-            txt_cmd.feedBack(client, u"Can't deactivate Parrot mode for invalid jid", mess_data)
+            txt_cmd.feedBack(
+                client, u"Can't deactivate Parrot mode for invalid jid", mess_data
+            )
             return False
 
-        link_right_jid = mess_data['to']
+        link_right_jid = mess_data["to"]
 
         self.removeParrot(client, link_left_jid)
         self.removeParrot(client, link_right_jid)
 
-        txt_cmd.feedBack(client, u"Parrot mode deactivated for {} and {}".format(unicode(link_left_jid), unicode(link_right_jid)), mess_data)
+        txt_cmd.feedBack(
+            client,
+            u"Parrot mode deactivated for {} and {}".format(
+                unicode(link_left_jid), unicode(link_right_jid)
+            ),
+            mess_data,
+        )
 
         return False
--- a/sat/plugins/plugin_exp_pubsub_hook.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_exp_pubsub_hook.py	Wed Jun 27 20:14:46 2018 +0200
@@ -24,9 +24,10 @@
 from sat.memory import persistent
 from twisted.words.protocols.jabber import jid
 from twisted.internet import defer
+
 log = getLogger(__name__)
 
-NS_PUBSUB_HOOK = 'PUBSUB_HOOK'
+NS_PUBSUB_HOOK = "PUBSUB_HOOK"
 
 PLUGIN_INFO = {
     C.PI_NAME: "PubSub Hook",
@@ -36,40 +37,48 @@
     C.PI_DEPENDENCIES: ["XEP-0060"],
     C.PI_MAIN: "PubsubHook",
     C.PI_HANDLER: "no",
-    C.PI_DESCRIPTION: _("""Experimental plugin to launch on action on Pubsub notifications""")
+    C.PI_DESCRIPTION: _(
+        """Experimental plugin to launch on action on Pubsub notifications"""
+    ),
 }
 
-# python module
-HOOK_TYPE_PYTHON = u'python'
+#  python module
+HOOK_TYPE_PYTHON = u"python"
 # python file path
-HOOK_TYPE_PYTHON_FILE = u'python_file'
+HOOK_TYPE_PYTHON_FILE = u"python_file"
 # python code directly
-HOOK_TYPE_PYTHON_CODE = u'python_code'
+HOOK_TYPE_PYTHON_CODE = u"python_code"
 HOOK_TYPES = (HOOK_TYPE_PYTHON, HOOK_TYPE_PYTHON_FILE, HOOK_TYPE_PYTHON_CODE)
 
 
 class PubsubHook(object):
-
     def __init__(self, host):
         log.info(_(u"PubSub Hook initialization"))
         self.host = host
         self.node_hooks = {}  # keep track of the number of hooks per node (for all profiles)
-        host.bridge.addMethod("psHookAdd", ".plugin",
-                              in_sign='ssssbs', out_sign='',
-                              method=self._addHook
-                              )
-        host.bridge.addMethod("psHookRemove", ".plugin",
-                              in_sign='sssss', out_sign='i',
-                              method=self._removeHook
-                              )
-        host.bridge.addMethod("psHookList", ".plugin",
-                              in_sign='s', out_sign='aa{ss}',
-                              method=self._listHooks
-                              )
+        host.bridge.addMethod(
+            "psHookAdd", ".plugin", in_sign="ssssbs", out_sign="", method=self._addHook
+        )
+        host.bridge.addMethod(
+            "psHookRemove",
+            ".plugin",
+            in_sign="sssss",
+            out_sign="i",
+            method=self._removeHook,
+        )
+        host.bridge.addMethod(
+            "psHookList",
+            ".plugin",
+            in_sign="s",
+            out_sign="aa{ss}",
+            method=self._listHooks,
+        )
 
     @defer.inlineCallbacks
     def profileConnected(self, client):
-        hooks = client._hooks = persistent.PersistentBinaryDict(NS_PUBSUB_HOOK, client.profile)
+        hooks = client._hooks = persistent.PersistentBinaryDict(
+            NS_PUBSUB_HOOK, client.profile
+        )
         client._hooks_temporary = {}
         yield hooks.load()
         for node in hooks:
@@ -85,10 +94,11 @@
             self.node_hooks[node] += 1
         else:
             # first hook on this node
-            self.host.plugins['XEP-0060'].addManagedNode(node, items_cb=self._itemsReceived)
+            self.host.plugins["XEP-0060"].addManagedNode(
+                node, items_cb=self._itemsReceived
+            )
             self.node_hooks[node] = 0
-            log.info(_(u"node manager installed on {node}").format(
-                node = node))
+            log.info(_(u"node manager installed on {node}").format(node=node))
 
     def _removeNodeManager(self, client, node):
         try:
@@ -98,34 +108,40 @@
         else:
             if self.node_hooks[node] == 0:
                 del self.node_hooks[node]
-                self.host.plugins['XEP-0060'].removeManagedNode(node, self._itemsReceived)
+                self.host.plugins["XEP-0060"].removeManagedNode(node, self._itemsReceived)
                 log.debug(_(u"hook removed"))
             else:
                 log.debug(_(u"node still needed for an other hook"))
 
     def installHook(self, client, service, node, hook_type, hook_arg, persistent):
         if hook_type not in HOOK_TYPES:
-            raise exceptions.DataError(_(u'{hook_type} is not handled').format(hook_type=hook_type))
+            raise exceptions.DataError(
+                _(u"{hook_type} is not handled").format(hook_type=hook_type)
+            )
         if hook_type != HOOK_TYPE_PYTHON_FILE:
-            raise NotImplementedError(_(u'{hook_type} hook type not implemented yet').format(hook_type=hook_type))
+            raise NotImplementedError(
+                _(u"{hook_type} hook type not implemented yet").format(
+                    hook_type=hook_type
+                )
+            )
         self._installNodeManager(client, node)
-        hook_data = {'service': service,
-                     'type': hook_type,
-                     'arg': hook_arg
-                    }
+        hook_data = {"service": service, "type": hook_type, "arg": hook_arg}
 
         if persistent:
-            hooks_list = client._hooks.setdefault(node,[])
+            hooks_list = client._hooks.setdefault(node, [])
             hooks_list.append(hook_data)
             client._hooks.force(node)
         else:
-            hooks_list = client._hooks_temporary.setdefault(node,[])
+            hooks_list = client._hooks_temporary.setdefault(node, [])
             hooks_list.append(hook_data)
 
-        log.info(_(u"{persistent} hook installed on {node} for {profile}").format(
-            persistent = _(u'persistent') if persistent else _(u'temporary'),
-            node = node,
-            profile = client.profile))
+        log.info(
+            _(u"{persistent} hook installed on {node} for {profile}").format(
+                persistent=_(u"persistent") if persistent else _(u"temporary"),
+                node=node,
+                profile=client.profile,
+            )
+        )
 
     def _itemsReceived(self, client, itemsEvent):
         node = itemsEvent.nodeIdentifier
@@ -134,24 +150,30 @@
                 continue
             hooks_list = hooks[node]
             for hook_data in hooks_list[:]:
-                if hook_data['service'] != itemsEvent.sender.userhostJID():
+                if hook_data["service"] != itemsEvent.sender.userhostJID():
                     continue
                 try:
-                    callback = hook_data['callback']
+                    callback = hook_data["callback"]
                 except KeyError:
                     # first time we get this hook, we create the callback
-                    hook_type = hook_data['type']
+                    hook_type = hook_data["type"]
                     try:
                         if hook_type == HOOK_TYPE_PYTHON_FILE:
                             hook_globals = {}
-                            execfile(hook_data['arg'], hook_globals)
-                            callback = hook_globals['hook']
+                            execfile(hook_data["arg"], hook_globals)
+                            callback = hook_globals["hook"]
                         else:
-                            raise NotImplementedError(_(u'{hook_type} hook type not implemented yet').format(
-                                hook_type=hook_type))
+                            raise NotImplementedError(
+                                _(u"{hook_type} hook type not implemented yet").format(
+                                    hook_type=hook_type
+                                )
+                            )
                     except Exception as e:
-                        log.warning(_(u"Can't load Pubsub hook at node {node}, it will be removed: {reason}").format(
-                            node=node, reason=e))
+                        log.warning(
+                            _(
+                                u"Can't load Pubsub hook at node {node}, it will be removed: {reason}"
+                            ).format(node=node, reason=e)
+                        )
                         hooks_list.remove(hook_data)
                         continue
 
@@ -159,14 +181,23 @@
                     try:
                         callback(self.host, client, item)
                     except Exception as e:
-                        log.warning(_(u"Error while running Pubsub hook for node {node}: {msg}").format(
-                            node = node,
-                            msg = e))
+                        log.warning(
+                            _(
+                                u"Error while running Pubsub hook for node {node}: {msg}"
+                            ).format(node=node, msg=e)
+                        )
 
     def _addHook(self, service, node, hook_type, hook_arg, persistent, profile):
         client = self.host.getClient(profile)
         service = jid.JID(service) if service else client.jid.userhostJID()
-        return self.addHook(client, service, unicode(node), unicode(hook_type), unicode(hook_arg), persistent)
+        return self.addHook(
+            client,
+            service,
+            unicode(node),
+            unicode(hook_type),
+            unicode(hook_arg),
+            persistent,
+        )
 
     def addHook(self, client, service, node, hook_type, hook_arg, persistent):
         r"""Add a hook which will be triggered on a pubsub notification
@@ -210,14 +241,18 @@
         for hooks in (client._hooks, client._hooks_temporary):
             if node in hooks:
                 for hook_data in hooks[node]:
-                    if (service != hook_data[u'service']
-                        or hook_type is not None and hook_type != hook_data[u'type']
-                        or hook_arg is not None and hook_arg != hook_data[u'arg']):
+                    if (
+                        service != hook_data[u"service"]
+                        or hook_type is not None
+                        and hook_type != hook_data[u"type"]
+                        or hook_arg is not None
+                        and hook_arg != hook_data[u"arg"]
+                    ):
                         continue
                     hooks[node].remove(hook_data)
                     removed += 1
                     if not hooks[node]:
-                        # no more hooks, we can remove the node
+                        #  no more hooks, we can remove the node
                         del hooks[node]
                         self._removeNodeManager(client, node)
                     else:
@@ -228,8 +263,8 @@
     def _listHooks(self, profile):
         hooks_list = self.listHooks(self.host.getClient(profile))
         for hook in hooks_list:
-            hook[u'service'] = hook[u'service'].full()
-            hook[u'persistent'] = C.boolConst(hook[u'persistent'])
+            hook[u"service"] = hook[u"service"].full()
+            hook[u"persistent"] = C.boolConst(hook[u"persistent"])
         return hooks_list
 
     def listHooks(self, client):
@@ -239,11 +274,13 @@
             persistent = hooks is client._hooks
             for node, hooks_data in hooks.iteritems():
                 for hook_data in hooks_data:
-                    hooks_list.append({u'service': hook_data[u'service'],
-                                       u'node': node,
-                                       u'type': hook_data[u'type'],
-                                       u'arg': hook_data[u'arg'],
-                                       u'persistent': persistent
-                                       })
+                    hooks_list.append(
+                        {
+                            u"service": hook_data[u"service"],
+                            u"node": node,
+                            u"type": hook_data[u"type"],
+                            u"arg": hook_data[u"arg"],
+                            u"persistent": persistent,
+                        }
+                    )
         return hooks_list
-
--- a/sat/plugins/plugin_exp_pubsub_schema.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_exp_pubsub_schema.py	Wed Jun 27 20:14:46 2018 +0200
@@ -27,6 +27,7 @@
 from twisted.words.protocols.jabber.xmlstream import XMPPHandler
 from twisted.internet import defer
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from wokkel import disco, iwokkel
 from wokkel import data_form
@@ -36,7 +37,7 @@
 import copy
 import itertools
 
-NS_SCHEMA = 'https://salut-a-toi/protocol/schema:0'
+NS_SCHEMA = "https://salut-a-toi/protocol/schema:0"
 
 PLUGIN_INFO = {
     C.PI_NAME: "PubSub Schema",
@@ -46,47 +47,63 @@
     C.PI_DEPENDENCIES: ["XEP-0060", "IDENTITY"],
     C.PI_MAIN: "PubsubSchema",
     C.PI_HANDLER: "yes",
-    C.PI_DESCRIPTION: _("""Handle Pubsub data schemas""")
+    C.PI_DESCRIPTION: _("""Handle Pubsub data schemas"""),
 }
 
 
 class PubsubSchema(object):
-
     def __init__(self, host):
         log.info(_(u"PubSub Schema initialization"))
         self.host = host
         self._p = self.host.plugins["XEP-0060"]
         self._i = self.host.plugins["IDENTITY"]
-        host.bridge.addMethod("psSchemaGet", ".plugin",
-                              in_sign='sss', out_sign='s',
-                              method=self._getSchema,
-                              async=True
-                              )
-        host.bridge.addMethod("psSchemaSet", ".plugin",
-                              in_sign='ssss', out_sign='',
-                              method=self._setSchema,
-                              async=True
-                              )
-        host.bridge.addMethod("psSchemaUIGet", ".plugin",
-                              in_sign='sss', out_sign='s',
-                              method=utils.partial(self._getUISchema, default_node=None),
-                              async=True
-                              )
-        host.bridge.addMethod("psItemsFormGet", ".plugin",
-                              in_sign='ssssiassa{ss}s', out_sign='(asa{ss})',
-                              method=self._getDataFormItems,
-                              async=True)
-        host.bridge.addMethod("psItemFormSend", ".plugin",
-                              in_sign='ssa{sas}ssa{ss}s', out_sign='s',
-                              method=self._sendDataFormItem,
-                              async=True)
+        host.bridge.addMethod(
+            "psSchemaGet",
+            ".plugin",
+            in_sign="sss",
+            out_sign="s",
+            method=self._getSchema,
+            async=True,
+        )
+        host.bridge.addMethod(
+            "psSchemaSet",
+            ".plugin",
+            in_sign="ssss",
+            out_sign="",
+            method=self._setSchema,
+            async=True,
+        )
+        host.bridge.addMethod(
+            "psSchemaUIGet",
+            ".plugin",
+            in_sign="sss",
+            out_sign="s",
+            method=utils.partial(self._getUISchema, default_node=None),
+            async=True,
+        )
+        host.bridge.addMethod(
+            "psItemsFormGet",
+            ".plugin",
+            in_sign="ssssiassa{ss}s",
+            out_sign="(asa{ss})",
+            method=self._getDataFormItems,
+            async=True,
+        )
+        host.bridge.addMethod(
+            "psItemFormSend",
+            ".plugin",
+            in_sign="ssa{sas}ssa{ss}s",
+            out_sign="s",
+            method=self._sendDataFormItem,
+            async=True,
+        )
 
     def getHandler(self, client):
         return SchemaHandler()
 
     def _getSchemaBridgeCb(self, schema_elt):
         if schema_elt is None:
-            return u''
+            return u""
         return schema_elt.toXml()
 
     def _getSchema(self, service, nodeIdentifier, profile_key=C.PROF_KEY_NONE):
@@ -98,11 +115,11 @@
 
     def _getSchemaCb(self, iq_elt):
         try:
-            schema_elt = next(iq_elt.elements(NS_SCHEMA, 'schema'))
+            schema_elt = next(iq_elt.elements(NS_SCHEMA, "schema"))
         except StopIteration:
-            raise exceptions.DataError('missing <schema> element')
+            raise exceptions.DataError("missing <schema> element")
         try:
-            x_elt = next(schema_elt.elements((data_form.NS_X_DATA, 'x')))
+            x_elt = next(schema_elt.elements((data_form.NS_X_DATA, "x")))
         except StopIteration:
             # there is not schema on this node
             return None
@@ -117,18 +134,26 @@
         @return (domish.Element, None): schema (<x> element)
             None if not schema has been set on this node
         """
-        iq_elt = client.IQ(u'get')
+        iq_elt = client.IQ(u"get")
         if service is not None:
-            iq_elt['to'] = service.full()
-        pubsub_elt = iq_elt.addElement((NS_SCHEMA, 'pubsub'))
-        schema_elt = pubsub_elt.addElement((NS_SCHEMA, 'schema'))
-        schema_elt['node'] = nodeIdentifier
+            iq_elt["to"] = service.full()
+        pubsub_elt = iq_elt.addElement((NS_SCHEMA, "pubsub"))
+        schema_elt = pubsub_elt.addElement((NS_SCHEMA, "schema"))
+        schema_elt["node"] = nodeIdentifier
         d = iq_elt.send()
         d.addCallback(self._getSchemaCb)
         return d
 
     @defer.inlineCallbacks
-    def getSchemaForm(self, client, service, nodeIdentifier, schema=None, form_type='form', copy_form=True):
+    def getSchemaForm(
+        self,
+        client,
+        service,
+        nodeIdentifier,
+        schema=None,
+        form_type="form",
+        copy_form=True,
+    ):
         """get data form from node's schema
 
         @param service(None, jid.JID): PubSub service
@@ -147,7 +172,11 @@
             log.debug(_(u"unspecified schema, we need to request it"))
             schema = yield self.getSchema(client, service, nodeIdentifier)
             if schema is None:
-                raise exceptions.DataError(_(u"no schema specified, and this node has no schema either, we can't construct the data form"))
+                raise exceptions.DataError(
+                    _(
+                        u"no schema specified, and this node has no schema either, we can't construct the data form"
+                    )
+                )
         elif isinstance(schema, data_form.Form):
             if copy_form:
                 schema = copy.deepcopy(schema)
@@ -156,17 +185,18 @@
         try:
             form = data_form.Form.fromElement(schema)
         except data_form.Error as e:
-            raise exceptions.DataError(_(u"Invalid Schema: {msg}").format(
-                msg = e))
+            raise exceptions.DataError(_(u"Invalid Schema: {msg}").format(msg=e))
         form.formType = form_type
         defer.returnValue(form)
 
     def schema2XMLUI(self, schema_elt):
         form = data_form.Form.fromElement(schema_elt)
-        xmlui = xml_tools.dataForm2XMLUI(form, '')
+        xmlui = xml_tools.dataForm2XMLUI(form, "")
         return xmlui
 
-    def _getUISchema(self, service, nodeIdentifier, default_node=None, profile_key=C.PROF_KEY_NONE):
+    def _getUISchema(
+        self, service, nodeIdentifier, default_node=None, profile_key=C.PROF_KEY_NONE
+    ):
         if not nodeIdentifier:
             if not default_node:
                 raise ValueError(_(u"nodeIndentifier needs to be set"))
@@ -185,7 +215,7 @@
     def _setSchema(self, service, nodeIdentifier, schema, profile_key=C.PROF_KEY_NONE):
         client = self.host.getClient(profile_key)
         service = None if not service else jid.JID(service)
-        schema = generic.parseXml(schema.encode('utf-8'))
+        schema = generic.parseXml(schema.encode("utf-8"))
         return self.setSchema(client, service, nodeIdentifier, schema)
 
     def setSchema(self, client, service, nodeIdentifier, schema):
@@ -196,31 +226,67 @@
         """
         iq_elt = client.IQ()
         if service is not None:
-            iq_elt['to'] = service.full()
-        pubsub_elt = iq_elt.addElement((NS_SCHEMA, 'pubsub'))
-        schema_elt = pubsub_elt.addElement((NS_SCHEMA, 'schema'))
-        schema_elt['node'] = nodeIdentifier
+            iq_elt["to"] = service.full()
+        pubsub_elt = iq_elt.addElement((NS_SCHEMA, "pubsub"))
+        schema_elt = pubsub_elt.addElement((NS_SCHEMA, "schema"))
+        schema_elt["node"] = nodeIdentifier
         if schema is not None:
             schema_elt.addChild(schema)
         return iq_elt.send()
 
-    def _getDataFormItems(self, form_ns='', service='', node='', schema='', max_items=10, item_ids=None, sub_id=None, extra_dict=None, profile_key=C.PROF_KEY_NONE):
+    def _getDataFormItems(
+        self,
+        form_ns="",
+        service="",
+        node="",
+        schema="",
+        max_items=10,
+        item_ids=None,
+        sub_id=None,
+        extra_dict=None,
+        profile_key=C.PROF_KEY_NONE,
+    ):
         client = self.host.getClient(profile_key)
         service = jid.JID(service) if service else None
         if not node:
-            raise exceptions.DataError(_(u'empty node is not allowed'))
+            raise exceptions.DataError(_(u"empty node is not allowed"))
         if schema:
-            schema = generic.parseXml(schema.encode('utf-8'))
+            schema = generic.parseXml(schema.encode("utf-8"))
         else:
             schema = None
         max_items = None if max_items == C.NO_LIMIT else max_items
         extra = self._p.parseExtra(extra_dict)
-        d = self.getDataFormItems(client, service, node, schema, max_items or None, item_ids, sub_id or None, extra.rsm_request, extra.extra, form_ns=form_ns or None)
+        d = self.getDataFormItems(
+            client,
+            service,
+            node,
+            schema,
+            max_items or None,
+            item_ids,
+            sub_id or None,
+            extra.rsm_request,
+            extra.extra,
+            form_ns=form_ns or None,
+        )
         d.addCallback(self._p.serItemsData)
         return d
 
     @defer.inlineCallbacks
-    def getDataFormItems(self, client, service, nodeIdentifier, schema=None, max_items=None, item_ids=None, sub_id=None, rsm_request=None, extra=None, default_node=None, form_ns=None, filters=None):
+    def getDataFormItems(
+        self,
+        client,
+        service,
+        nodeIdentifier,
+        schema=None,
+        max_items=None,
+        item_ids=None,
+        sub_id=None,
+        rsm_request=None,
+        extra=None,
+        default_node=None,
+        form_ns=None,
+        filters=None,
+    ):
         """Get items known as being data forms, and convert them to XMLUI
 
         @param schema(domish.Element, data_form.Form, None): schema of the node if known
@@ -237,15 +303,28 @@
         """
         if not nodeIdentifier:
             if not default_node:
-                raise ValueError(_(u"default_node must be set if nodeIdentifier is not set"))
+                raise ValueError(
+                    _(u"default_node must be set if nodeIdentifier is not set")
+                )
             nodeIdentifier = default_node
         # we need the initial form to get options of fields when suitable
-        schema_form = yield self.getSchemaForm(client, service, nodeIdentifier, schema, form_type='result', copy_form=False)
-        items_data = yield self._p.getItems(client, service, nodeIdentifier, max_items, item_ids, sub_id, rsm_request, extra)
+        schema_form = yield self.getSchemaForm(
+            client, service, nodeIdentifier, schema, form_type="result", copy_form=False
+        )
+        items_data = yield self._p.getItems(
+            client,
+            service,
+            nodeIdentifier,
+            max_items,
+            item_ids,
+            sub_id,
+            rsm_request,
+            extra,
+        )
         items, metadata = items_data
         items_xmlui = []
         for item_elt in items:
-            for x_elt in item_elt.elements((data_form.NS_X_DATA, u'x')):
+            for x_elt in item_elt.elements((data_form.NS_X_DATA, u"x")):
                 form = data_form.Form.fromElement(x_elt)
                 if form_ns and form.formNamespace != form_ns:
                     continue
@@ -254,28 +333,59 @@
                     schema_form,
                     # FIXME: conflicts with schema (i.e. if "id" or "publisher" already exists)
                     #        are not checked
-                    prepend = (('label', 'id'),('text', item_elt['id'], u'id'),
-                               ('label', 'publisher'),('text', item_elt.getAttribute('publisher',''), u'publisher')),
-                    filters = filters,
-                    )
+                    prepend=(
+                        ("label", "id"),
+                        ("text", item_elt["id"], u"id"),
+                        ("label", "publisher"),
+                        ("text", item_elt.getAttribute("publisher", ""), u"publisher"),
+                    ),
+                    filters=filters,
+                )
                 items_xmlui.append(xmlui)
                 break
         defer.returnValue((items_xmlui, metadata))
 
-
-    def _sendDataFormItem(self, service, nodeIdentifier, values, schema=None, item_id=None, extra=None, profile_key=C.PROF_KEY_NONE):
+    def _sendDataFormItem(
+        self,
+        service,
+        nodeIdentifier,
+        values,
+        schema=None,
+        item_id=None,
+        extra=None,
+        profile_key=C.PROF_KEY_NONE,
+    ):
         client = self.host.getClient(profile_key)
         service = None if not service else jid.JID(service)
         if schema:
-            schema = generic.parseXml(schema.encode('utf-8'))
+            schema = generic.parseXml(schema.encode("utf-8"))
         else:
             schema = None
-        d = self.sendDataFormItem(client, service, nodeIdentifier, values, schema, item_id or None, extra, deserialise=True)
-        d.addCallback(lambda ret: ret or u'')
+        d = self.sendDataFormItem(
+            client,
+            service,
+            nodeIdentifier,
+            values,
+            schema,
+            item_id or None,
+            extra,
+            deserialise=True,
+        )
+        d.addCallback(lambda ret: ret or u"")
         return d
 
     @defer.inlineCallbacks
-    def sendDataFormItem(self, client, service, nodeIdentifier, values, schema=None, item_id=None, extra=None, deserialise=False):
+    def sendDataFormItem(
+        self,
+        client,
+        service,
+        nodeIdentifier,
+        values,
+        schema=None,
+        item_id=None,
+        extra=None,
+        deserialise=False,
+    ):
         """Publish an item as a dataform when we know that there is a schema
 
         @param values(dict[key(unicode), [iterable[object], object]]): values set for the form
@@ -289,26 +399,34 @@
         other parameters as the same as for [self._p.sendItem]
         @return (unicode): id of the created item
         """
-        form = yield self.getSchemaForm(client, service, nodeIdentifier, schema, form_type='submit')
+        form = yield self.getSchemaForm(
+            client, service, nodeIdentifier, schema, form_type="submit"
+        )
 
         for name, values_list in values.iteritems():
             try:
                 field = form.fields[name]
             except KeyError:
-                log.warning(_(u"field {name} doesn't exist, ignoring it").format(name=name))
+                log.warning(
+                    _(u"field {name} doesn't exist, ignoring it").format(name=name)
+                )
                 continue
-            if isinstance(values_list, basestring) or not isinstance(values_list, Iterable):
+            if isinstance(values_list, basestring) or not isinstance(
+                values_list, Iterable
+            ):
                 values_list = [values_list]
             if deserialise:
-                if field.fieldType == 'boolean':
+                if field.fieldType == "boolean":
                     values_list = [C.bool(v) for v in values_list]
-                elif field.fieldType == 'text-multi':
+                elif field.fieldType == "text-multi":
                     # for text-multi, lines must be put on separate values
-                    values_list = list(itertools.chain(*[v.splitlines() for v in values_list]))
+                    values_list = list(
+                        itertools.chain(*[v.splitlines() for v in values_list])
+                    )
 
-                elif 'jid' in field.fieldType:
+                elif "jid" in field.fieldType:
                     values_list = [jid.JID(v) for v in values_list]
-            if 'list' in field.fieldType:
+            if "list" in field.fieldType:
                 # for lists, we check that given values are allowed in form
                 allowed_values = [o.value for o in field.options]
                 values_list = [v for v in values_list if v in allowed_values]
@@ -317,7 +435,9 @@
                     values_list = field.values
             field.values = values_list
 
-        yield self._p.sendItem(client, service, nodeIdentifier, form.toElement(), item_id, extra)
+        yield self._p.sendItem(
+            client, service, nodeIdentifier, form.toElement(), item_id, extra
+        )
 
     ## filters ##
     # filters useful for data form to XMLUI conversion #
@@ -327,7 +447,7 @@
         if not args[0]:
             # value is not filled: we use user part of publisher (if we have it)
             try:
-                publisher = jid.JID(form_xmlui.named_widgets['publisher'].value)
+                publisher = jid.JID(form_xmlui.named_widgets["publisher"].value)
             except (KeyError, RuntimeError):
                 pass
             else:
@@ -339,18 +459,20 @@
 
         main use case is using a textbox for labels
         """
-        if widget_type != u'textbox':
+        if widget_type != u"textbox":
             return widget_type, args, kwargs
-        widget_type = u'list'
-        options = [o for o in args.pop(0).split(u'\n') if o]
-        kwargs = {'options': options,
-                  'name': kwargs.get('name'),
-                  'styles': (u'noselect', u'extensible', u'reducible')}
+        widget_type = u"list"
+        options = [o for o in args.pop(0).split(u"\n") if o]
+        kwargs = {
+            "options": options,
+            "name": kwargs.get("name"),
+            "styles": (u"noselect", u"extensible", u"reducible"),
+        }
         return widget_type, args, kwargs
 
     def dateFilter(self, form_xmlui, widget_type, args, kwargs):
         """Convert a string with a date to a unix timestamp"""
-        if widget_type != u'string' or not args[0]:
+        if widget_type != u"string" or not args[0]:
             return widget_type, args, kwargs
         # we convert XMPP date to timestamp
         try:
@@ -377,7 +499,19 @@
 
         return client, service, node, max_items, extra, sub_id
 
-    def _get(self, service='', node='', max_items=10, item_ids=None, sub_id=None, extra=None, default_node=None, form_ns=None, filters=None, profile_key=C.PROF_KEY_NONE):
+    def _get(
+        self,
+        service="",
+        node="",
+        max_items=10,
+        item_ids=None,
+        sub_id=None,
+        extra=None,
+        default_node=None,
+        form_ns=None,
+        filters=None,
+        profile_key=C.PROF_KEY_NONE,
+    ):
         """Bridge method to retrieve data from node with schema
 
         this method is a helper so dependant plugins can use it directly
@@ -392,11 +526,16 @@
             extra = {}
         # XXX: Q&D way to get list for labels when displaying them, but text when we
         #      have to modify them
-        if C.bool(extra.get('labels_as_list', C.BOOL_FALSE)):
+        if C.bool(extra.get("labels_as_list", C.BOOL_FALSE)):
             filters = filters.copy()
-            filters[u'labels'] = self.textbox2ListFilter
-        client, service, node, max_items, extra, sub_id = self.prepareBridgeGet(service, node, max_items, sub_id, extra, profile_key)
-        d = self.getDataFormItems(client, service, node or None,
+            filters[u"labels"] = self.textbox2ListFilter
+        client, service, node, max_items, extra, sub_id = self.prepareBridgeGet(
+            service, node, max_items, sub_id, extra, profile_key
+        )
+        d = self.getDataFormItems(
+            client,
+            service,
+            node or None,
             max_items=max_items,
             item_ids=item_ids,
             sub_id=sub_id,
@@ -404,7 +543,8 @@
             extra=extra.extra,
             default_node=default_node,
             form_ns=form_ns,
-            filters=filters)
+            filters=filters,
+        )
         d.addCallback(self._p.serItemsData)
         return d
 
@@ -416,30 +556,65 @@
         client = self.host.getClient(profile_key)
         service = None if not service else jid.JID(service)
         if schema:
-            schema = generic.parseXml(schema.encode('utf-8'))
+            schema = generic.parseXml(schema.encode("utf-8"))
         else:
             schema = None
-        if extra and u'update' in extra:
-            extra[u'update'] = C.bool(extra[u'update'])
+        if extra and u"update" in extra:
+            extra[u"update"] = C.bool(extra[u"update"])
         return client, service, node or None, schema, item_id or None, extra
 
-    def _set(self, service, node, values, schema=None, item_id=None, extra=None, default_node=None, form_ns=None, fill_author=True, profile_key=C.PROF_KEY_NONE):
+    def _set(
+        self,
+        service,
+        node,
+        values,
+        schema=None,
+        item_id=None,
+        extra=None,
+        default_node=None,
+        form_ns=None,
+        fill_author=True,
+        profile_key=C.PROF_KEY_NONE,
+    ):
         """Bridge method to set item in node with schema
 
         this method is a helper so dependant plugins can use it directly
         when adding *Set methods
         """
-        client, service, node, schema, item_id, extra = self.prepareBridgeSet(service, node, schema, item_id, extra)
-        d = self.set(client, service, node, values, schema, item_id, extra,
-                     deserialise=True,
-                     form_ns=form_ns,
-                     default_node=default_node,
-                     fill_author=fill_author)
-        d.addCallback(lambda ret: ret or u'')
+        client, service, node, schema, item_id, extra = self.prepareBridgeSet(
+            service, node, schema, item_id, extra
+        )
+        d = self.set(
+            client,
+            service,
+            node,
+            values,
+            schema,
+            item_id,
+            extra,
+            deserialise=True,
+            form_ns=form_ns,
+            default_node=default_node,
+            fill_author=fill_author,
+        )
+        d.addCallback(lambda ret: ret or u"")
         return d
 
     @defer.inlineCallbacks
-    def set(self, client, service, node, values, schema, item_id, extra, deserialise, form_ns, default_node=None, fill_author=True):
+    def set(
+        self,
+        client,
+        service,
+        node,
+        values,
+        schema,
+        item_id,
+        extra,
+        deserialise,
+        form_ns,
+        default_node=None,
+        fill_author=True,
+    ):
         """Set an item in a node with a schema
 
         This method can be used directly by *Set methods added by dependant plugin
@@ -463,44 +638,56 @@
             node = default_node
         now = utils.xmpp_date()
         if not item_id:
-            values['created'] = now
-        elif extra.get(u'update', False):
+            values["created"] = now
+        elif extra.get(u"update", False):
             if item_id is None:
-                raise exceptions.DataError(_(u'if extra["update"] is set, item_id must be set too'))
+                raise exceptions.DataError(
+                    _(u'if extra["update"] is set, item_id must be set too')
+                )
             try:
                 # we get previous item
-                items_data = yield self._p.getItems(client, service, node, item_ids=[item_id])
+                items_data = yield self._p.getItems(
+                    client, service, node, item_ids=[item_id]
+                )
                 item_elt = items_data[0][0]
             except Exception as e:
-                log.warning(_(u"Can't get previous item, update ignored: {reason}").format(
-                    reason = e))
+                log.warning(
+                    _(u"Can't get previous item, update ignored: {reason}").format(
+                        reason=e
+                    )
+                )
             else:
                 # and parse it
                 form = data_form.findForm(item_elt, form_ns)
                 if form is None:
-                    log.warning(_(u"Can't parse previous item, update ignored: data form not found").format(
-                        reason = e))
+                    log.warning(
+                        _(
+                            u"Can't parse previous item, update ignored: data form not found"
+                        ).format(reason=e)
+                    )
                 else:
                     for name, field in form.fields.iteritems():
                         if name not in values:
-                            values[name] = u'\n'.join(unicode(v) for v in field.values)
+                            values[name] = u"\n".join(unicode(v) for v in field.values)
 
-        values['updated'] = now
+        values["updated"] = now
         if fill_author:
-            if not values.get('author'):
+            if not values.get("author"):
                 identity = yield self._i.getIdentity(client, client.jid)
-                values['author'] = identity['nick']
-            if not values.get('author_jid'):
-                values['author_jid'] = client.jid.full()
-        item_id = yield self.sendDataFormItem(client, service, node, values, schema, item_id, extra, deserialise)
+                values["author"] = identity["nick"]
+            if not values.get("author_jid"):
+                values["author_jid"] = client.jid.full()
+        item_id = yield self.sendDataFormItem(
+            client, service, node, values, schema, item_id, extra, deserialise
+        )
         defer.returnValue(item_id)
 
 
 class SchemaHandler(XMPPHandler):
     implements(iwokkel.IDisco)
 
-    def getDiscoInfo(self, requestor, service, nodeIdentifier=''):
+    def getDiscoInfo(self, requestor, service, nodeIdentifier=""):
         return [disco.DiscoFeature(NS_SCHEMA)]
 
-    def getDiscoItems(self, requestor, service, nodeIdentifier=''):
+    def getDiscoItems(self, requestor, service, nodeIdentifier=""):
         return []
--- a/sat/plugins/plugin_import.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_import.py	Wed Jun 27 20:14:46 2018 +0200
@@ -20,6 +20,7 @@
 from sat.core.i18n import _
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from twisted.internet import defer
 from sat.core import exceptions
@@ -37,14 +38,13 @@
     C.PI_DEPENDENCIES: [],
     C.PI_MAIN: "ImportPlugin",
     C.PI_HANDLER: "no",
-    C.PI_DESCRIPTION: _(u"""Generic import plugin, base for specialized importers""")
+    C.PI_DESCRIPTION: _(u"""Generic import plugin, base for specialized importers"""),
 }
 
-Importer = collections.namedtuple('Importer', ('callback', 'short_desc', 'long_desc'))
+Importer = collections.namedtuple("Importer", ("callback", "short_desc", "long_desc"))
 
 
 class ImportPlugin(object):
-
     def __init__(self, host):
         log.info(_("plugin Import initialization"))
         self.host = host
@@ -64,21 +64,51 @@
         @param name(unicode): import handler name
         """
         assert name == name.lower().strip()
-        log.info(_(u'initializing {name} import handler').format(name=name))
+        log.info(_(u"initializing {name} import handler").format(name=name))
         import_handler.name = name
         import_handler.register = partial(self.register, import_handler)
         import_handler.unregister = partial(self.unregister, import_handler)
         import_handler.importers = {}
+
         def _import(name, location, options, pubsub_service, pubsub_node, profile):
-            return self._doImport(import_handler, name, location, options, pubsub_service, pubsub_node, profile)
+            return self._doImport(
+                import_handler,
+                name,
+                location,
+                options,
+                pubsub_service,
+                pubsub_node,
+                profile,
+            )
+
         def _importList():
             return self.listImporters(import_handler)
+
         def _importDesc(name):
             return self.getDescription(import_handler, name)
 
-        self.host.bridge.addMethod(name + "Import", ".plugin", in_sign='ssa{ss}sss', out_sign='s', method=_import, async=True)
-        self.host.bridge.addMethod(name + "ImportList", ".plugin", in_sign='', out_sign='a(ss)', method=_importList)
-        self.host.bridge.addMethod(name + "ImportDesc", ".plugin", in_sign='s', out_sign='(ss)', method=_importDesc)
+        self.host.bridge.addMethod(
+            name + "Import",
+            ".plugin",
+            in_sign="ssa{ss}sss",
+            out_sign="s",
+            method=_import,
+            async=True,
+        )
+        self.host.bridge.addMethod(
+            name + "ImportList",
+            ".plugin",
+            in_sign="",
+            out_sign="a(ss)",
+            method=_importList,
+        )
+        self.host.bridge.addMethod(
+            name + "ImportDesc",
+            ".plugin",
+            in_sign="s",
+            out_sign="(ss)",
+            method=_importDesc,
+        )
 
     def getProgress(self, import_handler, progress_id, profile):
         client = self.host.getClient(profile)
@@ -87,7 +117,10 @@
     def listImporters(self, import_handler):
         importers = import_handler.importers.keys()
         importers.sort()
-        return [(name, import_handler.importers[name].short_desc) for name in import_handler.importers]
+        return [
+            (name, import_handler.importers[name].short_desc)
+            for name in import_handler.importers
+        ]
 
     def getDescription(self, import_handler, name):
         """Return import short and long descriptions
@@ -98,13 +131,24 @@
         try:
             importer = import_handler.importers[name]
         except KeyError:
-            raise exceptions.NotFound(u"{handler_name} importer not found [{name}]".format(
-                handler_name = import_handler.name,
-                name = name))
+            raise exceptions.NotFound(
+                u"{handler_name} importer not found [{name}]".format(
+                    handler_name=import_handler.name, name=name
+                )
+            )
         else:
             return importer.short_desc, importer.long_desc
 
-    def _doImport(self, import_handler, name, location, options, pubsub_service='', pubsub_node='', profile=C.PROF_KEY_NONE):
+    def _doImport(
+        self,
+        import_handler,
+        name,
+        location,
+        options,
+        pubsub_service="",
+        pubsub_node="",
+        profile=C.PROF_KEY_NONE,
+    ):
         client = self.host.getClient(profile)
         options = {key: unicode(value) for key, value in options.iteritems()}
         for option in import_handler.BOOL_OPTIONS:
@@ -116,12 +160,31 @@
             try:
                 options[option] = json.loads(options[option])
             except ValueError:
-                raise exceptions.DataError(_(u'invalid json option: {name}').format(name=option))
+                raise exceptions.DataError(
+                    _(u"invalid json option: {name}").format(name=option)
+                )
         pubsub_service = jid.JID(pubsub_service) if pubsub_service else None
-        return self.doImport(client, import_handler, unicode(name), unicode(location), options, pubsub_service, pubsub_node or None)
+        return self.doImport(
+            client,
+            import_handler,
+            unicode(name),
+            unicode(location),
+            options,
+            pubsub_service,
+            pubsub_node or None,
+        )
 
     @defer.inlineCallbacks
-    def doImport(self, client, import_handler, name, location, options=None, pubsub_service=None, pubsub_node=None):
+    def doImport(
+        self,
+        client,
+        import_handler,
+        name,
+        location,
+        options=None,
+        pubsub_service=None,
+        pubsub_node=None,
+    ):
         """Import data
 
         @param import_handler(object): instance of the import handler
@@ -142,7 +205,7 @@
             for opt_name, opt_default in import_handler.OPT_DEFAULTS.iteritems():
                 # we want a filled options dict, with all empty or False values removed
                 try:
-                    value =options[opt_name]
+                    value = options[opt_name]
                 except KeyError:
                     if opt_default:
                         options[opt_name] = opt_default
@@ -154,31 +217,61 @@
             importer = import_handler.importers[name]
         except KeyError:
             raise exceptions.NotFound(u"Importer [{}] not found".format(name))
-        items_import_data, items_count = yield importer.callback(client, location, options)
+        items_import_data, items_count = yield importer.callback(
+            client, location, options
+        )
         progress_id = unicode(uuid.uuid4())
         try:
             _import = client._import
         except AttributeError:
             _import = client._import = {}
         progress_data = _import.setdefault(import_handler.name, {})
-        progress_data[progress_id] = {u'position': '0'}
+        progress_data[progress_id] = {u"position": "0"}
         if items_count is not None:
-            progress_data[progress_id]['size'] = unicode(items_count)
-        metadata = {'name': u'{}: {}'.format(name, location),
-                    'direction': 'out',
-                    'type': import_handler.name.upper() + '_IMPORT'
-                   }
-        self.host.registerProgressCb(progress_id, partial(self.getProgress, import_handler), metadata, profile=client.profile)
+            progress_data[progress_id]["size"] = unicode(items_count)
+        metadata = {
+            "name": u"{}: {}".format(name, location),
+            "direction": "out",
+            "type": import_handler.name.upper() + "_IMPORT",
+        }
+        self.host.registerProgressCb(
+            progress_id,
+            partial(self.getProgress, import_handler),
+            metadata,
+            profile=client.profile,
+        )
         self.host.bridge.progressStarted(progress_id, metadata, client.profile)
-        session = {  # session data, can be used by importers
-            u'root_service': pubsub_service,
-            u'root_node': pubsub_node
+        session = {  #  session data, can be used by importers
+            u"root_service": pubsub_service,
+            u"root_node": pubsub_node,
         }
-        self.recursiveImport(client, import_handler, items_import_data, progress_id, session, options, None, pubsub_service, pubsub_node)
+        self.recursiveImport(
+            client,
+            import_handler,
+            items_import_data,
+            progress_id,
+            session,
+            options,
+            None,
+            pubsub_service,
+            pubsub_node,
+        )
         defer.returnValue(progress_id)
 
     @defer.inlineCallbacks
-    def recursiveImport(self, client, import_handler, items_import_data, progress_id, session, options, return_data=None, service=None, node=None, depth=0):
+    def recursiveImport(
+        self,
+        client,
+        import_handler,
+        items_import_data,
+        progress_id,
+        session,
+        options,
+        return_data=None,
+        service=None,
+        node=None,
+        depth=0,
+    ):
         """Do the import recursively
 
         @param import_handler(object): instance of the import handler
@@ -196,33 +289,37 @@
         if return_data is None:
             return_data = {}
         for idx, item_import_data in enumerate(items_import_data):
-            item_data = yield import_handler.importItem(client, item_import_data, session, options, return_data, service, node)
+            item_data = yield import_handler.importItem(
+                client, item_import_data, session, options, return_data, service, node
+            )
             yield import_handler.itemFilters(client, item_data, session, options)
-            recurse_kwargs = yield import_handler.importSubItems(client, item_import_data, item_data, session, options)
+            recurse_kwargs = yield import_handler.importSubItems(
+                client, item_import_data, item_data, session, options
+            )
             yield import_handler.publishItem(client, item_data, service, node, session)
 
             if recurse_kwargs is not None:
-                recurse_kwargs['client'] = client
-                recurse_kwargs['import_handler'] = import_handler
-                recurse_kwargs['progress_id'] = progress_id
-                recurse_kwargs['session'] = session
-                recurse_kwargs.setdefault('options', options)
-                recurse_kwargs['return_data'] = return_data
-                recurse_kwargs['depth'] = depth + 1
+                recurse_kwargs["client"] = client
+                recurse_kwargs["import_handler"] = import_handler
+                recurse_kwargs["progress_id"] = progress_id
+                recurse_kwargs["session"] = session
+                recurse_kwargs.setdefault("options", options)
+                recurse_kwargs["return_data"] = return_data
+                recurse_kwargs["depth"] = depth + 1
                 log.debug(_(u"uploading subitems"))
                 yield self.recursiveImport(**recurse_kwargs)
 
             if depth == 0:
-                client._import[import_handler.name][progress_id]['position'] = unicode(idx+1)
+                client._import[import_handler.name][progress_id]["position"] = unicode(
+                    idx + 1
+                )
 
         if depth == 0:
-            self.host.bridge.progressFinished(progress_id,
-                return_data,
-                client.profile)
+            self.host.bridge.progressFinished(progress_id, return_data, client.profile)
             self.host.removeProgressCb(progress_id, client.profile)
             del client._import[import_handler.name][progress_id]
 
-    def register(self, import_handler, name, callback, short_desc='', long_desc=''):
+    def register(self, import_handler, name, callback, short_desc="", long_desc=""):
         """Register an Importer method
 
         @param name(unicode): unique importer name, should indicate the software it can import and always lowercase
@@ -239,9 +336,11 @@
         """
         name = name.lower()
         if name in import_handler.importers:
-            raise exceptions.ConflictError(_(u"An {handler_name} importer with the name {name} already exist").format(
-                handler_name = import_handler.name,
-                name = name))
+            raise exceptions.ConflictError(
+                _(
+                    u"An {handler_name} importer with the name {name} already exist"
+                ).format(handler_name=import_handler.name, name=name)
+            )
         import_handler.importers[name] = Importer(callback, short_desc, long_desc)
 
     def unregister(self, import_handler, name):
--- a/sat/plugins/plugin_misc_account.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_misc_account.py	Wed Jun 27 20:14:46 2018 +0200
@@ -19,6 +19,7 @@
 
 from sat.core.i18n import _, D_
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from sat.core import exceptions
 from sat.tools import xml_tools
@@ -31,7 +32,7 @@
 from twisted.words.protocols.jabber import jid
 from sat.tools import email as sat_email
 
-# FIXME: this plugin code is old and need a cleaning
+#  FIXME: this plugin code is old and need a cleaning
 # TODO: account deletion/password change need testing
 
 
@@ -41,10 +42,10 @@
     C.PI_TYPE: "MISC",
     C.PI_PROTOCOLS: [],
     C.PI_DEPENDENCIES: ["XEP-0077"],
-    C.PI_RECOMMENDATIONS: ['GROUPBLOG'],
+    C.PI_RECOMMENDATIONS: ["GROUPBLOG"],
     C.PI_MAIN: "MiscAccount",
     C.PI_HANDLER: "no",
-    C.PI_DESCRIPTION: _(u"""SàT account creation""")
+    C.PI_DESCRIPTION: _(u"""SàT account creation"""),
 }
 
 CONFIG_SECTION = "plugin account"
@@ -53,22 +54,24 @@
 # all theses values (key=option name, value=default) can (and should) be overriden in sat.conf
 # in section CONFIG_SECTION
 
-default_conf = {"email_from": "NOREPLY@example.net",
-                "email_server": "localhost",
-                "email_sender_domain": "",
-                "email_port": 25,
-                "email_username": "",
-                "email_password": "",
-                "email_starttls": "false",
-                "email_auth": "false",
-                "email_admins_list": [],
-                "admin_email": "",
-                "new_account_server": "localhost",
-                "new_account_domain": "",  # use xmpp_domain if not found
-                "reserved_list": ['libervia'] # profiles which can't be used
-               }
+default_conf = {
+    "email_from": "NOREPLY@example.net",
+    "email_server": "localhost",
+    "email_sender_domain": "",
+    "email_port": 25,
+    "email_username": "",
+    "email_password": "",
+    "email_starttls": "false",
+    "email_auth": "false",
+    "email_admins_list": [],
+    "admin_email": "",
+    "new_account_server": "localhost",
+    "new_account_domain": "",  #  use xmpp_domain if not found
+    "reserved_list": ["libervia"],  # profiles which can't be used
+}
 
-WELCOME_MSG = D_(u"""Welcome to Libervia, the web interface of Salut à Toi.
+WELCOME_MSG = D_(
+    u"""Welcome to Libervia, the web interface of Salut à Toi.
 
 Your account on {domain} has been successfully created.
 This is a demonstration version to show you the current status of the project.
@@ -88,55 +91,101 @@
 
 Salut à Toi association
 https://www.salut-a-toi.org
-""")
+"""
+)
 
 DEFAULT_DOMAIN = u"example.net"
 
 
 class MiscAccount(object):
     """Account plugin: create a SàT + XMPP account, used by Libervia"""
+
     # XXX: This plugin was initialy a Q&D one used for the demo.
     # TODO: cleaning, separate email handling, more configuration/tests, fixes
 
-
     def __init__(self, host):
         log.info(_(u"Plugin Account initialization"))
         self.host = host
-        host.bridge.addMethod("registerSatAccount", ".plugin", in_sign='sss', out_sign='', method=self._registerAccount, async=True)
-        host.bridge.addMethod("getNewAccountDomain", ".plugin", in_sign='', out_sign='s', method=self.getNewAccountDomain, async=False)
-        host.bridge.addMethod("getAccountDialogUI", ".plugin", in_sign='s', out_sign='s', method=self._getAccountDialogUI, async=False)
-        host.bridge.addMethod("asyncConnectWithXMPPCredentials", ".plugin", in_sign='ss', out_sign='b', method=self.asyncConnectWithXMPPCredentials, async=True)
+        host.bridge.addMethod(
+            "registerSatAccount",
+            ".plugin",
+            in_sign="sss",
+            out_sign="",
+            method=self._registerAccount,
+            async=True,
+        )
+        host.bridge.addMethod(
+            "getNewAccountDomain",
+            ".plugin",
+            in_sign="",
+            out_sign="s",
+            method=self.getNewAccountDomain,
+            async=False,
+        )
+        host.bridge.addMethod(
+            "getAccountDialogUI",
+            ".plugin",
+            in_sign="s",
+            out_sign="s",
+            method=self._getAccountDialogUI,
+            async=False,
+        )
+        host.bridge.addMethod(
+            "asyncConnectWithXMPPCredentials",
+            ".plugin",
+            in_sign="ss",
+            out_sign="b",
+            method=self.asyncConnectWithXMPPCredentials,
+            async=True,
+        )
 
         self.fixEmailAdmins()
         self._sessions = Sessions()
 
-        self.__account_cb_id = host.registerCallback(self._accountDialogCb, with_data=True)
-        self.__change_password_id = host.registerCallback(self.__changePasswordCb, with_data=True)
+        self.__account_cb_id = host.registerCallback(
+            self._accountDialogCb, with_data=True
+        )
+        self.__change_password_id = host.registerCallback(
+            self.__changePasswordCb, with_data=True
+        )
 
         def deleteBlogCallback(posts, comments):
-            return lambda data, profile: self.__deleteBlogPostsCb(posts, comments, data, profile)
+            return lambda data, profile: self.__deleteBlogPostsCb(
+                posts, comments, data, profile
+            )
 
-        self.__delete_posts_id = host.registerCallback(deleteBlogCallback(True, False), with_data=True)
-        self.__delete_comments_id = host.registerCallback(deleteBlogCallback(False, True), with_data=True)
-        self.__delete_posts_comments_id = host.registerCallback(deleteBlogCallback(True, True), with_data=True)
+        self.__delete_posts_id = host.registerCallback(
+            deleteBlogCallback(True, False), with_data=True
+        )
+        self.__delete_comments_id = host.registerCallback(
+            deleteBlogCallback(False, True), with_data=True
+        )
+        self.__delete_posts_comments_id = host.registerCallback(
+            deleteBlogCallback(True, True), with_data=True
+        )
 
-        self.__delete_account_id = host.registerCallback(self.__deleteAccountCb, with_data=True)
-
+        self.__delete_account_id = host.registerCallback(
+            self.__deleteAccountCb, with_data=True
+        )
 
     # FIXME: remove this after some time, when the deprecated parameter is really abandoned
     def fixEmailAdmins(self):
         """Handle deprecated config option "admin_email" to fix the admin emails list"""
-        admin_email = self.getConfig('admin_email')
+        admin_email = self.getConfig("admin_email")
         if not admin_email:
             return
-        log.warning(u"admin_email parameter is deprecated, please use email_admins_list instead")
+        log.warning(
+            u"admin_email parameter is deprecated, please use email_admins_list instead"
+        )
         param_name = "email_admins_list"
         try:
             section = ""
             value = self.host.memory.getConfig(section, param_name, Exception)
         except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
             section = CONFIG_SECTION
-            value = self.host.memory.getConfig(section, param_name, default_conf[param_name])
+            value = self.host.memory.getConfig(
+                section, param_name, default_conf[param_name]
+            )
 
         value = set(value)
         value.add(admin_email)
@@ -193,7 +242,7 @@
         if not password or not profile:
             raise exceptions.DataError
 
-        if profile.lower() in self.getConfig('reserved_list'):
+        if profile.lower() in self.getConfig("reserved_list"):
             return defer.fail(Failure(exceptions.ConflictError))
 
         d = self.host.memory.createProfile(profile, password)
@@ -216,11 +265,15 @@
         else:
             jid_s = profile + u"@" + self.getNewAccountDomain()
             jid_ = jid.JID(jid_s)
-            d = self.host.plugins['XEP-0077'].registerNewAccount(jid_, password)
+            d = self.host.plugins["XEP-0077"].registerNewAccount(jid_, password)
 
         def setParams(dummy):
-            self.host.memory.setParam("JabberID", jid_s, "Connection", profile_key=profile)
-            d = self.host.memory.setParam("Password", password, "Connection", profile_key=profile)
+            self.host.memory.setParam(
+                "JabberID", jid_s, "Connection", profile_key=profile
+            )
+            d = self.host.memory.setParam(
+                "Password", password, "Connection", profile_key=profile
+            )
             return d
 
         def removeProfile(failure):
@@ -235,9 +288,11 @@
 
     def _sendEmailEb(self, failure_, email):
         # TODO: return error code to user
-        log.error(_(u"Failed to send account creation confirmation to {email}: {msg}").format(
-            email = email,
-            msg = failure_))
+        log.error(
+            _(u"Failed to send account creation confirmation to {email}: {msg}").format(
+                email=email, msg=failure_
+            )
+        )
 
     def sendEmails(self, email, profile):
         # time to send the email
@@ -245,43 +300,68 @@
         domain = self.getNewAccountDomain()
 
         # email to the administrators
-        admins_emails = self.getConfig('email_admins_list')
+        admins_emails = self.getConfig("email_admins_list")
         if not admins_emails:
-            log.warning(u"No known admin email, we can't send email to administrator(s).\nPlease fill email_admins_list parameter")
+            log.warning(
+                u"No known admin email, we can't send email to administrator(s).\nPlease fill email_admins_list parameter"
+            )
             d_admin = defer.fail(exceptions.DataError("no admin email"))
         else:
-            subject = _(u'New Libervia account created')
-            body = (u"""New account created: {profile} [{email}]""".format(
-                profile = profile,
+            subject = _(u"New Libervia account created")
+            body = u"""New account created: {profile} [{email}]""".format(
+                profile=profile,
                 # there is no email when an existing XMPP account is used
-                email = email or u"<no email>"))
+                email=email or u"<no email>",
+            )
             d_admin = sat_email.sendEmail(self.host, admins_emails, subject, body)
 
-        admins_emails_txt = u', '.join([u'<' + addr + u'>' for addr in admins_emails])
-        d_admin.addCallbacks(lambda dummy: log.debug(u"Account creation notification sent to admin(s) {}".format(admins_emails_txt)),
-                             lambda dummy: log.error(u"Failed to send account creation notification to admin {}".format(admins_emails_txt)))
+        admins_emails_txt = u", ".join([u"<" + addr + u">" for addr in admins_emails])
+        d_admin.addCallbacks(
+            lambda dummy: log.debug(
+                u"Account creation notification sent to admin(s) {}".format(
+                    admins_emails_txt
+                )
+            ),
+            lambda dummy: log.error(
+                u"Failed to send account creation notification to admin {}".format(
+                    admins_emails_txt
+                )
+            ),
+        )
         if not email:
             # TODO: if use register with an existing account, an XMPP message should be sent
             return d_admin
 
-        jid_s = self.host.memory.getParamA(u"JabberID", u"Connection", profile_key=profile)
-        subject = _(u'Your Libervia account has been created')
-        body = (_(WELCOME_MSG).format(profile=profile, jid=jid_s, domain=domain))
+        jid_s = self.host.memory.getParamA(
+            u"JabberID", u"Connection", profile_key=profile
+        )
+        subject = _(u"Your Libervia account has been created")
+        body = _(WELCOME_MSG).format(profile=profile, jid=jid_s, domain=domain)
 
         # XXX: this will not fail when the email address doesn't exist
         # FIXME: check email reception to validate email given by the user
         # FIXME: delete the profile if the email could not been sent?
         d_user = sat_email.sendEmail(self.host, [email], subject, body)
-        d_user.addCallbacks(lambda dummy: log.debug(u"Account creation confirmation sent to <{}>".format(email)),
-                            self._sendEmailEb)
+        d_user.addCallbacks(
+            lambda dummy: log.debug(
+                u"Account creation confirmation sent to <{}>".format(email)
+            ),
+            self._sendEmailEb,
+        )
         return defer.DeferredList([d_user, d_admin])
 
     def getNewAccountDomain(self):
         """get the domain that will be set to new account"""
 
-        domain = self.getConfig('new_account_domain') or self.getConfig('xmpp_domain', None)
+        domain = self.getConfig("new_account_domain") or self.getConfig(
+            "xmpp_domain", None
+        )
         if not domain:
-            log.warning(_(u'xmpp_domain needs to be set in sat.conf. Using "{default}" meanwhile').format(default=DEFAULT_DOMAIN))
+            log.warning(
+                _(
+                    u'xmpp_domain needs to be set in sat.conf. Using "{default}" meanwhile'
+                ).format(default=DEFAULT_DOMAIN)
+            )
             return DEFAULT_DOMAIN
         return domain
 
@@ -291,10 +371,17 @@
         @param profile: %(doc_profile)s
         @return: XML of the dialog
         """
-        form_ui = xml_tools.XMLUI("form", "tabs", title=D_("Manage your account"), submit_id=self.__account_cb_id)
+        form_ui = xml_tools.XMLUI(
+            "form",
+            "tabs",
+            title=D_("Manage your account"),
+            submit_id=self.__account_cb_id,
+        )
         tab_container = form_ui.current_container
 
-        tab_container.addTab("update", D_("Change your password"), container=xml_tools.PairsContainer)
+        tab_container.addTab(
+            "update", D_("Change your password"), container=xml_tools.PairsContainer
+        )
         form_ui.addLabel(D_("Current profile password"))
         form_ui.addPassword("current_passwd", value="")
         form_ui.addLabel(D_("New password"))
@@ -328,7 +415,9 @@
         @param data
         @param profile
         """
-        sat_cipher = yield self.host.memory.asyncGetParamA(C.PROFILE_PASS_PATH[1], C.PROFILE_PASS_PATH[0], profile_key=profile)
+        sat_cipher = yield self.host.memory.asyncGetParamA(
+            C.PROFILE_PASS_PATH[1], C.PROFILE_PASS_PATH[0], profile_key=profile
+        )
 
         @defer.inlineCallbacks
         def verify(attempt):
@@ -340,7 +429,7 @@
                 message = D_("The provided profile password doesn't match.")
             error_ui = xml_tools.XMLUI("popup", title=D_("Attempt failure"))
             error_ui.addText(message)
-            return {'xmlui': error_ui.toXml()}
+            return {"xmlui": error_ui.toXml()}
 
         # check for account deletion
         # FIXME: uncomment and fix these features
@@ -370,9 +459,9 @@
         """
 
         # check for password modification
-        current_passwd = data[xml_tools.SAT_FORM_PREFIX + 'current_passwd']
-        new_passwd1 = data[xml_tools.SAT_FORM_PREFIX + 'new_passwd1']
-        new_passwd2 = data[xml_tools.SAT_FORM_PREFIX + 'new_passwd2']
+        current_passwd = data[xml_tools.SAT_FORM_PREFIX + "current_passwd"]
+        new_passwd1 = data[xml_tools.SAT_FORM_PREFIX + "new_passwd1"]
+        new_passwd2 = data[xml_tools.SAT_FORM_PREFIX + "new_passwd2"]
         if new_passwd1 or new_passwd2:
             verified = yield verify(current_passwd)
             assert isinstance(verified, bool)
@@ -381,7 +470,11 @@
                     data = yield self.__changePassword(new_passwd1, profile=profile)
                     defer.returnValue(data)
                 else:
-                    defer.returnValue(error_ui(D_("The values entered for the new password are not equal.")))
+                    defer.returnValue(
+                        error_ui(
+                            D_("The values entered for the new password are not equal.")
+                        )
+                    )
             defer.returnValue(error_ui())
 
         defer.returnValue({})
@@ -392,11 +485,22 @@
         @param password (str): the new password
         @param profile (str): %(doc_profile)s
         """
-        session_id, dummy = self._sessions.newSession({'new_password': password}, profile=profile)
-        form_ui = xml_tools.XMLUI("form", title=D_("Change your password?"), submit_id=self.__change_password_id, session_id=session_id)
-        form_ui.addText(D_("Note for advanced users: this will actually change both your SàT profile password AND your XMPP account password."))
+        session_id, dummy = self._sessions.newSession(
+            {"new_password": password}, profile=profile
+        )
+        form_ui = xml_tools.XMLUI(
+            "form",
+            title=D_("Change your password?"),
+            submit_id=self.__change_password_id,
+            session_id=session_id,
+        )
+        form_ui.addText(
+            D_(
+                "Note for advanced users: this will actually change both your SàT profile password AND your XMPP account password."
+            )
+        )
         form_ui.addText(D_("Continue with changing the password?"))
-        return {'xmlui': form_ui.toXml()}
+        return {"xmlui": form_ui.toXml()}
 
     def __changePasswordCb(self, data, profile):
         """Actually change the user XMPP account and SàT profile password
@@ -404,22 +508,33 @@
         @profile (str): %(doc_profile)s
         """
         client = self.host.getClient(profile)
-        password = self._sessions.profileGet(data['session_id'], profile)['new_password']
-        del self._sessions[data['session_id']]
+        password = self._sessions.profileGet(data["session_id"], profile)["new_password"]
+        del self._sessions[data["session_id"]]
 
         def passwordChanged(dummy):
-            d = self.host.memory.setParam(C.PROFILE_PASS_PATH[1], password, C.PROFILE_PASS_PATH[0], profile_key=profile)
-            d.addCallback(lambda dummy: self.host.memory.setParam("Password", password, "Connection", profile_key=profile))
+            d = self.host.memory.setParam(
+                C.PROFILE_PASS_PATH[1],
+                password,
+                C.PROFILE_PASS_PATH[0],
+                profile_key=profile,
+            )
+            d.addCallback(
+                lambda dummy: self.host.memory.setParam(
+                    "Password", password, "Connection", profile_key=profile
+                )
+            )
             confirm_ui = xml_tools.XMLUI("popup", title=D_("Confirmation"))
             confirm_ui.addText(D_("Your password has been changed."))
-            return defer.succeed({'xmlui': confirm_ui.toXml()})
+            return defer.succeed({"xmlui": confirm_ui.toXml()})
 
         def errback(failure):
             error_ui = xml_tools.XMLUI("popup", title=D_("Error"))
-            error_ui.addText(D_("Your password could not be changed: %s") % failure.getErrorMessage())
-            return defer.succeed({'xmlui': error_ui.toXml()})
+            error_ui.addText(
+                D_("Your password could not be changed: %s") % failure.getErrorMessage()
+            )
+            return defer.succeed({"xmlui": error_ui.toXml()})
 
-        d = self.host.plugins['XEP-0077'].changePassword(client, password)
+        d = self.host.plugins["XEP-0077"].changePassword(client, password)
         d.addCallbacks(passwordChanged, errback)
         return d
 
@@ -427,12 +542,31 @@
         """Ask for a confirmation before deleting the XMPP account and SàT profile
         @param profile
         """
-        form_ui = xml_tools.XMLUI("form", title=D_("Delete your account?"), submit_id=self.__delete_account_id)
-        form_ui.addText(D_("If you confirm this dialog, you will be disconnected and then your XMPP account AND your SàT profile will both be DELETED."))
-        target = D_('contact list, messages history, blog posts and comments' if 'GROUPBLOG' in self.host.plugins else D_('contact list and messages history'))
-        form_ui.addText(D_("All your data stored on %(server)s, including your %(target)s will be erased.") % {'server': self.getNewAccountDomain(), 'target': target})
-        form_ui.addText(D_("There is no other confirmation dialog, this is the very last one! Are you sure?"))
-        return {'xmlui': form_ui.toXml()}
+        form_ui = xml_tools.XMLUI(
+            "form", title=D_("Delete your account?"), submit_id=self.__delete_account_id
+        )
+        form_ui.addText(
+            D_(
+                "If you confirm this dialog, you will be disconnected and then your XMPP account AND your SàT profile will both be DELETED."
+            )
+        )
+        target = D_(
+            "contact list, messages history, blog posts and comments"
+            if "GROUPBLOG" in self.host.plugins
+            else D_("contact list and messages history")
+        )
+        form_ui.addText(
+            D_(
+                "All your data stored on %(server)s, including your %(target)s will be erased."
+            )
+            % {"server": self.getNewAccountDomain(), "target": target}
+        )
+        form_ui.addText(
+            D_(
+                "There is no other confirmation dialog, this is the very last one! Are you sure?"
+            )
+        )
+        return {"xmlui": form_ui.toXml()}
 
     def __deleteAccountCb(self, data, profile):
         """Actually delete the XMPP account and SàT profile
@@ -441,18 +575,25 @@
         @param profile
         """
         client = self.host.getClient(profile)
+
         def userDeleted(dummy):
 
             # FIXME: client should be disconnected at this point, so 2 next loop should be removed (to be confirmed)
             for jid_ in client.roster._jids:  # empty roster
                 client.presence.unsubscribe(jid_)
 
-            for jid_ in self.host.memory.getWaitingSub(profile):  # delete waiting subscriptions
+            for jid_ in self.host.memory.getWaitingSub(
+                profile
+            ):  # delete waiting subscriptions
                 self.host.memory.delWaitingSub(jid_)
 
-            delete_profile = lambda: self.host.memory.asyncDeleteProfile(profile, force=True)
-            if 'GROUPBLOG' in self.host.plugins:
-                d = self.host.plugins['GROUPBLOG'].deleteAllGroupBlogsAndComments(profile_key=profile)
+            delete_profile = lambda: self.host.memory.asyncDeleteProfile(
+                profile, force=True
+            )
+            if "GROUPBLOG" in self.host.plugins:
+                d = self.host.plugins["GROUPBLOG"].deleteAllGroupBlogsAndComments(
+                    profile_key=profile
+                )
                 d.addCallback(lambda dummy: delete_profile())
             else:
                 delete_profile()
@@ -461,10 +602,13 @@
 
         def errback(failure):
             error_ui = xml_tools.XMLUI("popup", title=D_("Error"))
-            error_ui.addText(D_("Your XMPP account could not be deleted: %s") % failure.getErrorMessage())
-            return defer.succeed({'xmlui': error_ui.toXml()})
+            error_ui.addText(
+                D_("Your XMPP account could not be deleted: %s")
+                % failure.getErrorMessage()
+            )
+            return defer.succeed({"xmlui": error_ui.toXml()})
 
-        d = self.host.plugins['XEP-0077'].unregister(client, jid.JID(client.jid.host))
+        d = self.host.plugins["XEP-0077"].unregister(client, jid.JID(client.jid.host))
         d.addCallbacks(userDeleted, errback)
         return d
 
@@ -477,20 +621,60 @@
         """
         if posts:
             if comments:  # delete everything
-                form_ui = xml_tools.XMLUI("form", title=D_("Delete all your (micro-)blog posts and comments?"), submit_id=self.__delete_posts_comments_id)
-                form_ui.addText(D_("If you confirm this dialog, all the (micro-)blog data you submitted will be erased."))
-                form_ui.addText(D_("These are the public and private posts and comments you sent to any group."))
-                form_ui.addText(D_("There is no other confirmation dialog, this is the very last one! Are you sure?"))
+                form_ui = xml_tools.XMLUI(
+                    "form",
+                    title=D_("Delete all your (micro-)blog posts and comments?"),
+                    submit_id=self.__delete_posts_comments_id,
+                )
+                form_ui.addText(
+                    D_(
+                        "If you confirm this dialog, all the (micro-)blog data you submitted will be erased."
+                    )
+                )
+                form_ui.addText(
+                    D_(
+                        "These are the public and private posts and comments you sent to any group."
+                    )
+                )
+                form_ui.addText(
+                    D_(
+                        "There is no other confirmation dialog, this is the very last one! Are you sure?"
+                    )
+                )
             else:  # delete only the posts
-                form_ui = xml_tools.XMLUI("form", title=D_("Delete all your (micro-)blog posts?"), submit_id=self.__delete_posts_id)
-                form_ui.addText(D_("If you confirm this dialog, all the public and private posts you sent to any group will be erased."))
-                form_ui.addText(D_("There is no other confirmation dialog, this is the very last one! Are you sure?"))
+                form_ui = xml_tools.XMLUI(
+                    "form",
+                    title=D_("Delete all your (micro-)blog posts?"),
+                    submit_id=self.__delete_posts_id,
+                )
+                form_ui.addText(
+                    D_(
+                        "If you confirm this dialog, all the public and private posts you sent to any group will be erased."
+                    )
+                )
+                form_ui.addText(
+                    D_(
+                        "There is no other confirmation dialog, this is the very last one! Are you sure?"
+                    )
+                )
         elif comments:  # delete only the comments
-            form_ui = xml_tools.XMLUI("form", title=D_("Delete all your (micro-)blog comments?"), submit_id=self.__delete_comments_id)
-            form_ui.addText(D_("If you confirm this dialog, all the public and private comments you made on other people's posts will be erased."))
-            form_ui.addText(D_("There is no other confirmation dialog, this is the very last one! Are you sure?"))
+            form_ui = xml_tools.XMLUI(
+                "form",
+                title=D_("Delete all your (micro-)blog comments?"),
+                submit_id=self.__delete_comments_id,
+            )
+            form_ui.addText(
+                D_(
+                    "If you confirm this dialog, all the public and private comments you made on other people's posts will be erased."
+                )
+            )
+            form_ui.addText(
+                D_(
+                    "There is no other confirmation dialog, this is the very last one! Are you sure?"
+                )
+            )
 
-        return {'xmlui': form_ui.toXml()}
+        return {"xmlui": form_ui.toXml()}
 
     def __deleteBlogPostsCb(self, posts, comments, data, profile):
         """Actually delete the XMPP account and SàT profile
@@ -500,26 +684,39 @@
         """
         if posts:
             if comments:
-                target = D_('blog posts and comments')
-                d = self.host.plugins['GROUPBLOG'].deleteAllGroupBlogsAndComments(profile_key=profile)
+                target = D_("blog posts and comments")
+                d = self.host.plugins["GROUPBLOG"].deleteAllGroupBlogsAndComments(
+                    profile_key=profile
+                )
             else:
-                target = D_('blog posts')
-                d = self.host.plugins['GROUPBLOG'].deleteAllGroupBlogs(profile_key=profile)
+                target = D_("blog posts")
+                d = self.host.plugins["GROUPBLOG"].deleteAllGroupBlogs(
+                    profile_key=profile
+                )
         elif comments:
-            target = D_('comments')
-            d = self.host.plugins['GROUPBLOG'].deleteAllGroupBlogsComments(profile_key=profile)
+            target = D_("comments")
+            d = self.host.plugins["GROUPBLOG"].deleteAllGroupBlogsComments(
+                profile_key=profile
+            )
 
         def deleted(result):
             ui = xml_tools.XMLUI("popup", title=D_("Deletion confirmation"))
             # TODO: change the message when delete/retract notifications are done with XEP-0060
-            ui.addText(D_("Your %(target)s have been deleted.") % {'target': target})
-            ui.addText(D_("Known issue of the demo version: you need to refresh the page to make the deleted posts actually disappear."))
-            return defer.succeed({'xmlui': ui.toXml()})
+            ui.addText(D_("Your %(target)s have been deleted.") % {"target": target})
+            ui.addText(
+                D_(
+                    "Known issue of the demo version: you need to refresh the page to make the deleted posts actually disappear."
+                )
+            )
+            return defer.succeed({"xmlui": ui.toXml()})
 
         def errback(failure):
             error_ui = xml_tools.XMLUI("popup", title=D_("Error"))
-            error_ui.addText(D_("Your %(target)s could not be deleted: %(message)s") % {'target': target, 'message': failure.getErrorMessage()})
-            return defer.succeed({'xmlui': error_ui.toXml()})
+            error_ui.addText(
+                D_("Your %(target)s could not be deleted: %(message)s")
+                % {"target": target, "message": failure.getErrorMessage()}
+            )
+            return defer.succeed({"xmlui": error_ui.toXml()})
 
         d.addCallbacks(deleted, errback)
         return d
@@ -541,16 +738,21 @@
             raise exceptions.ConflictError
 
         d = self.createProfile(password, jid_s, jid_s)
-        d.addCallback(lambda dummy: self.host.memory.getProfileName(jid_s))  # checks if the profile has been successfuly created
+        d.addCallback(
+            lambda dummy: self.host.memory.getProfileName(jid_s)
+        )  # checks if the profile has been successfuly created
         d.addCallback(self.host.connect, password, {}, 0)
 
-
         def connected(result):
             self.sendEmails(None, profile=jid_s)
             return result
 
-        def removeProfile(failure):  # profile has been successfully created but the XMPP credentials are wrong!
-            log.debug("Removing previously auto-created profile: %s" % failure.getErrorMessage())
+        def removeProfile(
+            failure
+        ):  # profile has been successfully created but the XMPP credentials are wrong!
+            log.debug(
+                "Removing previously auto-created profile: %s" % failure.getErrorMessage()
+            )
             self.host.memory.asyncDeleteProfile(jid_s)
             raise failure
 
--- a/sat/plugins/plugin_misc_android.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_misc_android.py	Wed Jun 27 20:14:46 2018 +0200
@@ -20,6 +20,7 @@
 from sat.core.i18n import _, D_
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from sat.core import exceptions
 import sys
@@ -32,7 +33,9 @@
     C.PI_TYPE: C.PLUG_TYPE_MISC,
     C.PI_MAIN: "AndroidPlugin",
     C.PI_HANDLER: "no",
-    C.PI_DESCRIPTION: D_("""Manage Android platform specificities, like pause or notifications""")
+    C.PI_DESCRIPTION: D_(
+        """Manage Android platform specificities, like pause or notifications"""
+    ),
 }
 
 if sys.platform != "android":
@@ -44,6 +47,7 @@
 PARAM_VIBRATE_NAME = "vibrate"
 PARAM_VIBRATE_LABEL = D_(u"Vibrate on notifications")
 
+
 class AndroidPlugin(object):
 
     params = """
@@ -55,40 +59,41 @@
     </individual>
     </params>
     """.format(
-        category_name = PARAM_VIBRATE_CATEGORY,
-        category_label = D_(PARAM_VIBRATE_CATEGORY),
-        param_name = PARAM_VIBRATE_NAME,
-        param_label = PARAM_VIBRATE_LABEL,
-        )
+        category_name=PARAM_VIBRATE_CATEGORY,
+        category_label=D_(PARAM_VIBRATE_CATEGORY),
+        param_name=PARAM_VIBRATE_NAME,
+        param_label=PARAM_VIBRATE_LABEL,
+    )
 
     def __init__(self, host):
         log.info(_("plugin Android initialization"))
         self.host = host
         host.memory.updateParams(self.params)
-        self.cagou_status_fd = open('.cagou_status', 'rb')
-        self.cagou_status = mmap.mmap(self.cagou_status_fd.fileno(), 1, prot=mmap.PROT_READ)
+        self.cagou_status_fd = open(".cagou_status", "rb")
+        self.cagou_status = mmap.mmap(
+            self.cagou_status_fd.fileno(), 1, prot=mmap.PROT_READ
+        )
         # we set a low priority because we want the notification to be sent after all plugins have done their job
         host.trigger.add("MessageReceived", self.messageReceivedTrigger, priority=-1000)
 
     @property
     def cagou_active(self):
-        # 'R' status means Cagou is running in front
-        return self.cagou_status[0] == 'R'
+        #  'R' status means Cagou is running in front
+        return self.cagou_status[0] == "R"
 
     def _notifyMessage(self, mess_data, client):
         # send notification if there is a message and it is not a groupchat
-        if mess_data['message'] and mess_data['type'] != C.MESS_TYPE_GROUPCHAT:
-            message = mess_data['message'].itervalues().next()
+        if mess_data["message"] and mess_data["type"] != C.MESS_TYPE_GROUPCHAT:
+            message = mess_data["message"].itervalues().next()
             try:
-                subject = mess_data['subject'].itervalues().next()
+                subject = mess_data["subject"].itervalues().next()
             except StopIteration:
-                subject = u'Cagou new message'
+                subject = u"Cagou new message"
 
-            notification.notify(
-                title = subject,
-                message = message
-                )
-            if self.host.memory.getParamA(PARAM_VIBRATE_NAME, PARAM_VIBRATE_CATEGORY, profile_key=client.profile):
+            notification.notify(title=subject, message=message)
+            if self.host.memory.getParamA(
+                PARAM_VIBRATE_NAME, PARAM_VIBRATE_CATEGORY, profile_key=client.profile
+            ):
                 vibrator.vibrate()
         return mess_data
 
--- a/sat/plugins/plugin_misc_debug.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_misc_debug.py	Wed Jun 27 20:14:46 2018 +0200
@@ -19,6 +19,7 @@
 
 from sat.core.i18n import _
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from sat.core.constants import Const as C
 import json
@@ -31,17 +32,21 @@
     C.PI_DEPENDENCIES: [],
     C.PI_MAIN: "Debug",
     C.PI_HANDLER: "no",
-    C.PI_DESCRIPTION: _("""Set of method to make development and debugging easier""")
+    C.PI_DESCRIPTION: _("""Set of method to make development and debugging easier"""),
 }
 
 
 class Debug(object):
-
     def __init__(self, host):
         log.info(_("Plugin Debug initialization"))
         self.host = host
-        host.bridge.addMethod("debugFakeSignal", ".plugin", in_sign='sss', out_sign='', method=self._fakeSignal)
-
+        host.bridge.addMethod(
+            "debugFakeSignal",
+            ".plugin",
+            in_sign="sss",
+            out_sign="",
+            method=self._fakeSignal,
+        )
 
     def _fakeSignal(self, signal, arguments, profile_key):
         """send a signal from backend
@@ -56,6 +61,3 @@
             profile = self.host.memory.getProfileName(profile_key)
             args.append(profile)
         method(*args)
-
-
-
--- a/sat/plugins/plugin_misc_extra_pep.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_misc_extra_pep.py	Wed Jun 27 20:14:46 2018 +0200
@@ -20,6 +20,7 @@
 from sat.core.i18n import _, D_
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from sat.memory import params
 from twisted.words.protocols.jabber import jid
@@ -34,7 +35,7 @@
     C.PI_RECOMMENDATIONS: [],
     C.PI_MAIN: "ExtraPEP",
     C.PI_HANDLER: "no",
-    C.PI_DESCRIPTION: _(u"""Display messages from extra PEP services""")
+    C.PI_DESCRIPTION: _(u"""Display messages from extra PEP services"""),
 }
 
 
@@ -57,11 +58,11 @@
     </individual>
     </params>
     """ % {
-        'category_name': PARAM_KEY,
-        'category_label': D_(PARAM_KEY),
-        'param_name': PARAM_NAME,
-        'param_label': D_(PARAM_LABEL),
-        'jids': u"\n".join({elt.toXml() for elt in params.createJidElts(PARAM_DEFAULT)})
+        "category_name": PARAM_KEY,
+        "category_label": D_(PARAM_KEY),
+        "param_name": PARAM_NAME,
+        "param_label": D_(PARAM_LABEL),
+        "jids": u"\n".join({elt.toXml() for elt in params.createJidElts(PARAM_DEFAULT)}),
     }
 
     def __init__(self, host):
--- a/sat/plugins/plugin_misc_file.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_misc_file.py	Wed Jun 27 20:14:46 2018 +0200
@@ -20,6 +20,7 @@
 from sat.core.i18n import _, D_
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from sat.core import exceptions
 from sat.tools import xml_tools
@@ -37,38 +38,67 @@
     C.PI_MODES: C.PLUG_MODE_BOTH,
     C.PI_MAIN: "FilePlugin",
     C.PI_HANDLER: "no",
-    C.PI_DESCRIPTION: _("""File Tansfer Management:
-This plugin manage the various ways of sending a file, and choose the best one.""")
+    C.PI_DESCRIPTION: _(
+        """File Tansfer Management:
+This plugin manage the various ways of sending a file, and choose the best one."""
+    ),
 }
 
 
-SENDING = D_(u'Please select a file to send to {peer}')
-SENDING_TITLE = D_(u'File sending')
-CONFIRM = D_(u'{peer} wants to send the file "{name}" to you:\n{desc}\n\nThe file has a size of {size_human}\n\nDo you accept ?')
-CONFIRM_TITLE = D_(u'Confirm file transfer')
-CONFIRM_OVERWRITE = D_(u'File {} already exists, are you sure you want to overwrite ?')
-CONFIRM_OVERWRITE_TITLE = D_(u'File exists')
+SENDING = D_(u"Please select a file to send to {peer}")
+SENDING_TITLE = D_(u"File sending")
+CONFIRM = D_(
+    u'{peer} wants to send the file "{name}" to you:\n{desc}\n\nThe file has a size of {size_human}\n\nDo you accept ?'
+)
+CONFIRM_TITLE = D_(u"Confirm file transfer")
+CONFIRM_OVERWRITE = D_(u"File {} already exists, are you sure you want to overwrite ?")
+CONFIRM_OVERWRITE_TITLE = D_(u"File exists")
 SECURITY_LIMIT = 30
 
-PROGRESS_ID_KEY = 'progress_id'
+PROGRESS_ID_KEY = "progress_id"
 
 
 class FilePlugin(object):
-    File=stream.SatFile
+    File = stream.SatFile
 
     def __init__(self, host):
         log.info(_("plugin File initialization"))
         self.host = host
-        host.bridge.addMethod("fileSend", ".plugin", in_sign='ssssa{ss}s', out_sign='a{ss}', method=self._fileSend, async=True)
+        host.bridge.addMethod(
+            "fileSend",
+            ".plugin",
+            in_sign="ssssa{ss}s",
+            out_sign="a{ss}",
+            method=self._fileSend,
+            async=True,
+        )
         self._file_callbacks = []
-        host.importMenu((D_("Action"), D_("send file")), self._fileSendMenu, security_limit=10, help_string=D_("Send a file"), type_=C.MENU_SINGLE)
+        host.importMenu(
+            (D_("Action"), D_("send file")),
+            self._fileSendMenu,
+            security_limit=10,
+            help_string=D_("Send a file"),
+            type_=C.MENU_SINGLE,
+        )
 
-    def _fileSend(self, peer_jid_s, filepath, name="", file_desc="", extra=None, profile=C.PROF_KEY_NONE):
+    def _fileSend(
+        self,
+        peer_jid_s,
+        filepath,
+        name="",
+        file_desc="",
+        extra=None,
+        profile=C.PROF_KEY_NONE,
+    ):
         client = self.host.getClient(profile)
-        return self.fileSend(client, jid.JID(peer_jid_s), filepath, name or None, file_desc or None, extra)
+        return self.fileSend(
+            client, jid.JID(peer_jid_s), filepath, name or None, file_desc or None, extra
+        )
 
     @defer.inlineCallbacks
-    def fileSend(self, client, peer_jid, filepath, filename=None, file_desc=None, extra=None):
+    def fileSend(
+        self, client, peer_jid, filepath, filename=None, file_desc=None, extra=None
+    ):
         """Send a file using best available method
 
         @param peer_jid(jid.JID): jid of the destinee
@@ -81,22 +111,34 @@
         if not os.path.isfile(filepath):
             raise exceptions.DataError(u"The given path doesn't link to a file")
         if not filename:
-            filename = os.path.basename(filepath) or '_'
+            filename = os.path.basename(filepath) or "_"
         for namespace, callback, priority, method_name in self._file_callbacks:
             has_feature = yield self.host.hasFeature(client, namespace, peer_jid)
             if has_feature:
-                log.info(u"{name} method will be used to send the file".format(name=method_name))
-                progress_id = yield callback(client, peer_jid, filepath, filename, file_desc, extra)
-                defer.returnValue({'progress': progress_id})
+                log.info(
+                    u"{name} method will be used to send the file".format(
+                        name=method_name
+                    )
+                )
+                progress_id = yield callback(
+                    client, peer_jid, filepath, filename, file_desc, extra
+                )
+                defer.returnValue({"progress": progress_id})
         msg = u"Can't find any method to send file to {jid}".format(jid=peer_jid.full())
         log.warning(msg)
-        defer.returnValue({'xmlui': xml_tools.note(u"Can't transfer file", msg, C.XMLUI_DATA_LVL_WARNING).toXml()})
+        defer.returnValue(
+            {
+                "xmlui": xml_tools.note(
+                    u"Can't transfer file", msg, C.XMLUI_DATA_LVL_WARNING
+                ).toXml()
+            }
+        )
 
     def _onFileChoosed(self, client, peer_jid, data):
         cancelled = C.bool(data.get("cancelled", C.BOOL_FALSE))
         if cancelled:
             return
-        path=data['path']
+        path = data["path"]
         return self.fileSend(client, peer_jid, path)
 
     def _fileSendMenu(self, data, profile):
@@ -105,20 +147,28 @@
         @param profile: %(doc_profile)s
         """
         try:
-            jid_ = jid.JID(data['jid'])
+            jid_ = jid.JID(data["jid"])
         except RuntimeError:
             raise exceptions.DataError(_("Invalid JID"))
 
-        file_choosed_id = self.host.registerCallback(lambda data, profile: self._onFileChoosed(self.host.getClient(profile), jid_, data), with_data=True, one_shot=True)
+        file_choosed_id = self.host.registerCallback(
+            lambda data, profile: self._onFileChoosed(
+                self.host.getClient(profile), jid_, data
+            ),
+            with_data=True,
+            one_shot=True,
+        )
         xml_ui = xml_tools.XMLUI(
             C.XMLUI_DIALOG,
-            dialog_opt = {
+            dialog_opt={
                 C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_FILE,
-                C.XMLUI_DATA_MESS: _(SENDING).format(peer=jid_.full())},
-            title = _(SENDING_TITLE),
-            submit_id = file_choosed_id)
+                C.XMLUI_DATA_MESS: _(SENDING).format(peer=jid_.full()),
+            },
+            title=_(SENDING_TITLE),
+            submit_id=file_choosed_id,
+        )
 
-        return {'xmlui': xml_ui.toXml()}
+        return {"xmlui": xml_ui.toXml()}
 
     def register(self, namespace, callback, priority=0, method_name=None):
         """Register a fileSending method
@@ -130,8 +180,12 @@
         """
         for data in self._file_callbacks:
             if namespace == data[0]:
-                raise exceptions.ConflictError(u'A method with this namespace is already registered')
-        self._file_callbacks.append((namespace, callback, priority, method_name or namespace))
+                raise exceptions.ConflictError(
+                    u"A method with this namespace is already registered"
+                )
+        self._file_callbacks.append(
+            (namespace, callback, priority, method_name or namespace)
+        )
         self._file_callbacks.sort(key=lambda data: data[2], reverse=True)
 
     def unregister(self, namespace):
@@ -148,29 +202,31 @@
         """create SatFile or FileStremaObject for the requested file and fill suitable data
         """
         if stream_object:
-            assert 'stream_object' not in transfer_data
-            transfer_data['stream_object'] = stream.FileStreamObject(
+            assert "stream_object" not in transfer_data
+            transfer_data["stream_object"] = stream.FileStreamObject(
                 self.host,
                 client,
                 file_path,
-                mode='wb',
+                mode="wb",
                 uid=file_data[PROGRESS_ID_KEY],
-                size=file_data['size'],
-                data_cb = file_data.get('data_cb'),
-                )
+                size=file_data["size"],
+                data_cb=file_data.get("data_cb"),
+            )
         else:
-            assert 'file_obj' not in transfer_data
-            transfer_data['file_obj'] = stream.SatFile(
+            assert "file_obj" not in transfer_data
+            transfer_data["file_obj"] = stream.SatFile(
                 self.host,
                 client,
                 file_path,
-                mode='wb',
+                mode="wb",
                 uid=file_data[PROGRESS_ID_KEY],
-                size=file_data['size'],
-                data_cb = file_data.get('data_cb'),
-                )
+                size=file_data["size"],
+                data_cb=file_data.get("data_cb"),
+            )
 
-    def _gotConfirmation(self, data, client, peer_jid, transfer_data, file_data, stream_object):
+    def _gotConfirmation(
+        self, data, client, peer_jid, transfer_data, file_data, stream_object
+    ):
         """Called when the permission and dest path have been received
 
         @param peer_jid(jid.JID): jid of the file sender
@@ -181,17 +237,20 @@
             False if user wants to cancel
             if file exists ask confirmation and call again self._getDestDir if needed
         """
-        if data.get('cancelled', False):
+        if data.get("cancelled", False):
             return False
-        path = data['path']
-        file_data['file_path'] = file_path = os.path.join(path, file_data['name'])
-        log.debug(u'destination file path set to {}'.format(file_path))
+        path = data["path"]
+        file_data["file_path"] = file_path = os.path.join(path, file_data["name"])
+        log.debug(u"destination file path set to {}".format(file_path))
 
         # we manage case where file already exists
         if os.path.exists(file_path):
+
             def check_overwrite(overwrite):
                 if overwrite:
-                    self.openFileWrite(client, file_path, transfer_data, file_data, stream_object)
+                    self.openFileWrite(
+                        client, file_path, transfer_data, file_data, stream_object
+                    )
                     return True
                 else:
                     return self.getDestDir(client, peer_jid, transfer_data, file_data)
@@ -200,12 +259,14 @@
                 self.host,
                 _(CONFIRM_OVERWRITE).format(file_path),
                 _(CONFIRM_OVERWRITE_TITLE),
-                action_extra={'meta_from_jid': peer_jid.full(),
-                              'meta_type': C.META_TYPE_OVERWRITE,
-                              'meta_progress_id': file_data[PROGRESS_ID_KEY]
-                             },
+                action_extra={
+                    "meta_from_jid": peer_jid.full(),
+                    "meta_type": C.META_TYPE_OVERWRITE,
+                    "meta_progress_id": file_data[PROGRESS_ID_KEY],
+                },
                 security_limit=SECURITY_LIMIT,
-                profile=client.profile)
+                profile=client.profile,
+            )
             exists_d.addCallback(check_overwrite)
             return exists_d
 
@@ -239,24 +300,38 @@
             a stream.FileStreamObject will be used
         return (defer.Deferred): True if transfer is accepted
         """
-        cont,ret_value = self.host.trigger.returnPoint("FILE_getDestDir", client, peer_jid, transfer_data, file_data, stream_object)
+        cont, ret_value = self.host.trigger.returnPoint(
+            "FILE_getDestDir", client, peer_jid, transfer_data, file_data, stream_object
+        )
         if not cont:
             return ret_value
-        filename = file_data['name']
-        assert filename and not '/' in filename
+        filename = file_data["name"]
+        assert filename and not "/" in filename
         assert PROGRESS_ID_KEY in file_data
         # human readable size
-        file_data['size_human'] = u'{:.6n} Mio'.format(float(file_data['size'])/(1024**2))
-        d = xml_tools.deferDialog(self.host,
+        file_data["size_human"] = u"{:.6n} Mio".format(
+            float(file_data["size"]) / (1024 ** 2)
+        )
+        d = xml_tools.deferDialog(
+            self.host,
             _(CONFIRM).format(peer=peer_jid.full(), **file_data),
             _(CONFIRM_TITLE),
             type_=C.XMLUI_DIALOG_FILE,
             options={C.XMLUI_DATA_FILETYPE: C.XMLUI_DATA_FILETYPE_DIR},
-            action_extra={'meta_from_jid': peer_jid.full(),
-                          'meta_type': C.META_TYPE_FILE,
-                          'meta_progress_id': file_data[PROGRESS_ID_KEY]
-                         },
+            action_extra={
+                "meta_from_jid": peer_jid.full(),
+                "meta_type": C.META_TYPE_FILE,
+                "meta_progress_id": file_data[PROGRESS_ID_KEY],
+            },
             security_limit=SECURITY_LIMIT,
-            profile=client.profile)
-        d.addCallback(self._gotConfirmation, client, peer_jid, transfer_data, file_data, stream_object)
+            profile=client.profile,
+        )
+        d.addCallback(
+            self._gotConfirmation,
+            client,
+            peer_jid,
+            transfer_data,
+            file_data,
+            stream_object,
+        )
         return d
--- a/sat/plugins/plugin_misc_groupblog.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_misc_groupblog.py	Wed Jun 27 20:14:46 2018 +0200
@@ -20,6 +20,7 @@
 from sat.core.i18n import _
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from twisted.internet import defer
 from sat.core import exceptions
@@ -32,10 +33,12 @@
 except ImportError:
     from wokkel.subprotocols import XMPPHandler
 
-NS_PUBSUB = 'http://jabber.org/protocol/pubsub'
-NS_GROUPBLOG = 'http://salut-a-toi.org/protocol/groupblog'
-#NS_PUBSUB_EXP = 'http://goffi.org/protocol/pubsub' #for non official features
-NS_PUBSUB_EXP = NS_PUBSUB  # XXX: we can't use custom namespace as Wokkel's PubSubService use official NS
+NS_PUBSUB = "http://jabber.org/protocol/pubsub"
+NS_GROUPBLOG = "http://salut-a-toi.org/protocol/groupblog"
+# NS_PUBSUB_EXP = 'http://goffi.org/protocol/pubsub' #for non official features
+NS_PUBSUB_EXP = (
+    NS_PUBSUB
+)  # XXX: we can't use custom namespace as Wokkel's PubSubService use official NS
 NS_PUBSUB_GROUPBLOG = NS_PUBSUB_EXP + "#groupblog"
 NS_PUBSUB_ITEM_CONFIG = NS_PUBSUB_EXP + "#item-config"
 
@@ -48,7 +51,7 @@
     C.PI_DEPENDENCIES: ["XEP-0277"],
     C.PI_MAIN: "GroupBlog",
     C.PI_HANDLER: "yes",
-    C.PI_DESCRIPTION: _("""Implementation of microblogging fine permissions""")
+    C.PI_DESCRIPTION: _("""Implementation of microblogging fine permissions"""),
 }
 
 
@@ -74,7 +77,11 @@
             yield self.host.checkFeatures(client, (NS_PUBSUB_GROUPBLOG,))
         except exceptions.FeatureNotFound:
             client.server_groupblog_available = False
-            log.warning(_(u"Server is not able to manage item-access pubsub, we can't use group blog"))
+            log.warning(
+                _(
+                    u"Server is not able to manage item-access pubsub, we can't use group blog"
+                )
+            )
         else:
             client.server_groupblog_available = True
             log.info(_(u"Server can manage group blogs"))
@@ -85,7 +92,7 @@
         except exceptions.ProfileNotSetError:
             return {}
         try:
-            return {'available': C.boolConst(client.server_groupblog_available)}
+            return {"available": C.boolConst(client.server_groupblog_available)}
         except AttributeError:
             if self.host.isConnected(profile):
                 log.debug("Profile is not connected, service is not checked yet")
@@ -100,7 +107,11 @@
             return
         access_model = config_form.get(self._p.OPT_ACCESS_MODEL, self._p.ACCESS_OPEN)
         if access_model == self._p.ACCESS_PUBLISHER_ROSTER:
-            data_format.iter2dict('group', config_form.fields[self._p.OPT_ROSTER_GROUPS_ALLOWED].values, microblog_data)
+            data_format.iter2dict(
+                "group",
+                config_form.fields[self._p.OPT_ROSTER_GROUPS_ALLOWED].values,
+                microblog_data,
+            )
 
     def _data2entryTrigger(self, client, mb_data, entry_elt, item_elt):
         """Build fine access permission if needed
@@ -108,14 +119,16 @@
         This trigger check if "group*" key are present,
         and create a fine item config to restrict view to these groups
         """
-        groups = list(data_format.dict2iter('group', mb_data))
+        groups = list(data_format.dict2iter("group", mb_data))
         if not groups:
             return
         if not client.server_groupblog_available:
             raise exceptions.CancelError(u"GroupBlog is not available")
         log.debug(u"This entry use group blog")
-        form = data_form.Form('submit', formNamespace=NS_PUBSUB_ITEM_CONFIG)
-        access = data_form.Field(None, self._p.OPT_ACCESS_MODEL, value=self._p.ACCESS_PUBLISHER_ROSTER)
+        form = data_form.Form("submit", formNamespace=NS_PUBSUB_ITEM_CONFIG)
+        access = data_form.Field(
+            None, self._p.OPT_ACCESS_MODEL, value=self._p.ACCESS_PUBLISHER_ROSTER
+        )
         allowed = data_form.Field(None, self._p.OPT_ROSTER_GROUPS_ALLOWED, values=groups)
         form.addField(access)
         form.addField(allowed)
@@ -128,14 +141,16 @@
         """
         if "group" in mb_data:
             options[self._p.OPT_ACCESS_MODEL] = self._p.ACCESS_PUBLISHER_ROSTER
-            options[self._p.OPT_ROSTER_GROUPS_ALLOWED] = list(data_format.dict2iter('group', mb_data))
+            options[self._p.OPT_ROSTER_GROUPS_ALLOWED] = list(
+                data_format.dict2iter("group", mb_data)
+            )
 
 
 class GroupBlog_handler(XMPPHandler):
     implements(iwokkel.IDisco)
 
-    def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
+    def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
         return [disco.DiscoFeature(NS_GROUPBLOG)]
 
-    def getDiscoItems(self, requestor, target, nodeIdentifier=''):
+    def getDiscoItems(self, requestor, target, nodeIdentifier=""):
         return []
--- a/sat/plugins/plugin_misc_identity.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_misc_identity.py	Wed Jun 27 20:14:46 2018 +0200
@@ -22,6 +22,7 @@
 from sat.core.constants import Const as C
 from sat.core import exceptions
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from twisted.internet import defer
 from twisted.words.protocols.jabber import jid
@@ -31,24 +32,37 @@
 PLUGIN_INFO = {
     C.PI_NAME: "Identity Plugin",
     C.PI_IMPORT_NAME: "IDENTITY",
-    C.PI_TYPE: C.PLUG_TYPE_MISC ,
+    C.PI_TYPE: C.PLUG_TYPE_MISC,
     C.PI_PROTOCOLS: [],
     C.PI_DEPENDENCIES: ["XEP-0054"],
     C.PI_RECOMMENDATIONS: [],
     C.PI_MAIN: "Identity",
     C.PI_HANDLER: "no",
-    C.PI_DESCRIPTION: _("""Identity manager""")
+    C.PI_DESCRIPTION: _("""Identity manager"""),
 }
 
 
 class Identity(object):
-
     def __init__(self, host):
         log.info(_(u"Plugin Identity initialization"))
         self.host = host
-        self._v = host.plugins[u'XEP-0054']
-        host.bridge.addMethod(u"identityGet", u".plugin", in_sign=u'ss', out_sign=u'a{ss}', method=self._getIdentity, async=True)
-        host.bridge.addMethod(u"identitySet", u".plugin", in_sign=u'a{ss}s', out_sign=u'', method=self._setIdentity, async=True)
+        self._v = host.plugins[u"XEP-0054"]
+        host.bridge.addMethod(
+            u"identityGet",
+            u".plugin",
+            in_sign=u"ss",
+            out_sign=u"a{ss}",
+            method=self._getIdentity,
+            async=True,
+        )
+        host.bridge.addMethod(
+            u"identitySet",
+            u".plugin",
+            in_sign=u"a{ss}s",
+            out_sign=u"",
+            method=self._setIdentity,
+            async=True,
+        )
 
     def _getIdentity(self, jid_str, profile):
         jid_ = jid.JID(jid_str)
@@ -70,23 +84,25 @@
         # we first check roster
         roster_item = yield client.roster.getItem(jid_.userhostJID())
         if roster_item is not None and roster_item.name:
-            id_data[u'nick'] = roster_item.name
+            id_data[u"nick"] = roster_item.name
         elif jid_.resource and self._v.isRoom(client, jid_):
-            id_data[u'nick'] = jid_.resource
+            id_data[u"nick"] = jid_.resource
         else:
-            # and finally then vcard
+            #  and finally then vcard
             nick = yield self._v.getNick(client, jid_)
-            id_data[u'nick'] = nick if nick else jid_.user.capitalize()
+            id_data[u"nick"] = nick if nick else jid_.user.capitalize()
 
         try:
-            avatar_path = id_data[u'avatar'] = yield self._v.getAvatar(client, jid_, cache_only=False)
+            avatar_path = id_data[u"avatar"] = yield self._v.getAvatar(
+                client, jid_, cache_only=False
+            )
         except exceptions.NotFound:
             pass
         else:
             if avatar_path:
-                id_data[u'avatar_basename'] = os.path.basename(avatar_path)
+                id_data[u"avatar_basename"] = os.path.basename(avatar_path)
             else:
-                del id_data[u'avatar']
+                del id_data[u"avatar"]
 
         defer.returnValue(id_data)
 
@@ -101,8 +117,7 @@
             - nick: nickname
                 the vCard will be updated
         """
-        if id_data.keys() != [u'nick']:
-            raise NotImplementedError(u'Only nick can be updated for now')
-        if u'nick' in id_data:
-            return self._v.setNick(client, id_data[u'nick'])
-
+        if id_data.keys() != [u"nick"]:
+            raise NotImplementedError(u"Only nick can be updated for now")
+        if u"nick" in id_data:
+            return self._v.setNick(client, id_data[u"nick"])
--- a/sat/plugins/plugin_misc_imap.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_misc_imap.py	Wed Jun 27 20:14:46 2018 +0200
@@ -20,6 +20,7 @@
 from sat.core.i18n import _
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from twisted.internet import protocol, defer
 from twisted.cred import portal, checkers, credentials
@@ -41,12 +42,14 @@
     C.PI_DEPENDENCIES: ["Maildir"],
     C.PI_MAIN: "IMAP_server",
     C.PI_HANDLER: "no",
-    C.PI_DESCRIPTION: _("""Create an Imap server that you can use to read your "normal" type messages""")
+    C.PI_DESCRIPTION: _(
+        """Create an Imap server that you can use to read your "normal" type messages"""
+    ),
 }
 
 
 class IMAP_server(object):
-    #TODO: connect profile on mailbox request, once password is accepted
+    # TODO: connect profile on mailbox request, once password is accepted
 
     params = """
     <params>
@@ -62,7 +65,7 @@
         log.info(_("Plugin Imap Server initialization"))
         self.host = host
 
-        #parameters
+        # parameters
         host.memory.updateParams(self.params)
 
         port = int(self.host.memory.getParamA("IMAP Port", "Mail Server"))
@@ -76,7 +79,7 @@
     implements(imap4.IMessage)
 
     def __init__(self, uid, flags, mess_fp):
-        log.debug('Message Init')
+        log.debug("Message Init")
         self.uid = uid
         self.flags = flags
         self.mess_fp = mess_fp
@@ -85,22 +88,22 @@
     def getUID(self):
         """Retrieve the unique identifier associated with this message.
         """
-        log.debug('getUID (message)')
+        log.debug("getUID (message)")
         return self.uid
 
     def getFlags(self):
         """Retrieve the flags associated with this message.
         @return: The flags, represented as strings.
         """
-        log.debug('getFlags')
+        log.debug("getFlags")
         return self.flags
 
     def getInternalDate(self):
         """Retrieve the date internally associated with this message.
         @return: An RFC822-formatted date string.
         """
-        log.debug('getInternalDate')
-        return self.message['Date']
+        log.debug("getInternalDate")
+        return self.message["Date"]
 
     def getHeaders(self, negate, *names):
         """Retrieve a group of message headers.
@@ -109,32 +112,33 @@
         should be omitted from the return value, rather than included.
         @return: A mapping of header field names to header field values
         """
-        log.debug(u'getHeaders %s - %s' % (negate, names))
+        log.debug(u"getHeaders %s - %s" % (negate, names))
         final_dict = {}
         to_check = [name.lower() for name in names]
         for header in self.message.keys():
-            if (negate and not header.lower() in to_check) or \
-                    (not negate and header.lower() in to_check):
+            if (negate and not header.lower() in to_check) or (
+                not negate and header.lower() in to_check
+            ):
                 final_dict[header] = self.message[header]
         return final_dict
 
     def getBodyFile(self):
         """Retrieve a file object containing only the body of this message.
         """
-        log.debug('getBodyFile')
+        log.debug("getBodyFile")
         return StringIO(self.message.get_payload())
 
     def getSize(self):
         """Retrieve the total size, in octets, of this message.
         """
-        log.debug('getSize')
+        log.debug("getSize")
         self.mess_fp.seek(0, os.SEEK_END)
         return self.mess_fp.tell()
 
     def isMultipart(self):
         """Indicate whether this message has subparts.
         """
-        log.debug('isMultipart')
+        log.debug("isMultipart")
         return False
 
     def getSubPart(self, part):
@@ -142,7 +146,7 @@
         @param part: The number of the part to retrieve, indexed from 0.
         @return: The specified sub-part.
         """
-        log.debug('getSubPart')
+        log.debug("getSubPart")
         return TypeError
 
 
@@ -152,10 +156,12 @@
     def __init__(self, host, name, profile):
         self.host = host
         self.listeners = set()
-        log.debug(u'Mailbox init (%s)' % name)
+        log.debug(u"Mailbox init (%s)" % name)
         if name != "INBOX":
             raise imap4.MailboxException("Only INBOX is managed for the moment")
-        self.mailbox = self.host.plugins["Maildir"].accessMessageBox(name, self.messageNew, profile)
+        self.mailbox = self.host.plugins["Maildir"].accessMessageBox(
+            name, self.messageNew, profile
+        )
 
     def messageNew(self):
         """Called when a new message is in the mailbox"""
@@ -167,13 +173,13 @@
     def getUIDValidity(self):
         """Return the unique validity identifier for this mailbox.
         """
-        log.debug('getUIDValidity')
+        log.debug("getUIDValidity")
         return 0
 
     def getUIDNext(self):
         """Return the likely UID for the next message added to this mailbox.
         """
-        log.debug('getUIDNext')
+        log.debug("getUIDNext")
         return self.mailbox.getNextUid()
 
     def getUID(self, message):
@@ -181,14 +187,14 @@
         @param message: The message sequence number
         @return: The UID of the message.
         """
-        log.debug(u'getUID (%i)' % message)
-        #return self.mailbox.getUid(message-1) #XXX: it seems that this method get uid and not message sequence number
+        log.debug(u"getUID (%i)" % message)
+        # return self.mailbox.getUid(message-1) #XXX: it seems that this method get uid and not message sequence number
         return message
 
     def getMessageCount(self):
         """Return the number of messages in this mailbox.
         """
-        log.debug('getMessageCount')
+        log.debug("getMessageCount")
         ret = self.mailbox.getMessageCount()
         log.debug("count = %i" % ret)
         return ret
@@ -196,26 +202,26 @@
     def getRecentCount(self):
         """Return the number of messages with the 'Recent' flag.
         """
-        log.debug('getRecentCount')
-        return len(self.mailbox.getMessageIdsWithFlag('\\Recent'))
+        log.debug("getRecentCount")
+        return len(self.mailbox.getMessageIdsWithFlag("\\Recent"))
 
     def getUnseenCount(self):
         """Return the number of messages with the 'Unseen' flag.
         """
-        log.debug('getUnseenCount')
-        return self.getMessageCount() - len(self.mailbox.getMessageIdsWithFlag('\\SEEN'))
+        log.debug("getUnseenCount")
+        return self.getMessageCount() - len(self.mailbox.getMessageIdsWithFlag("\\SEEN"))
 
     def isWriteable(self):
         """Get the read/write status of the mailbox.
         @return: A true value if write permission is allowed, a false value otherwise.
         """
-        log.debug('isWriteable')
+        log.debug("isWriteable")
         return True
 
     def destroy(self):
         """Called before this mailbox is deleted, permanently.
         """
-        log.debug('destroy')
+        log.debug("destroy")
 
     def requestStatus(self, names):
         """Return status information about this mailbox.
@@ -227,7 +233,7 @@
         information up would be costly, a deferred whose callback will
         eventually be passed this dictionary is returned instead.
         """
-        log.debug('requestStatus')
+        log.debug("requestStatus")
         return imap4.statusRequestHelper(self, names)
 
     def addListener(self, listener):
@@ -237,7 +243,7 @@
         @param listener: An object to add to the set of those which will
         be notified when the contents of this mailbox change.
         """
-        log.debug(u'addListener %s' % listener)
+        log.debug(u"addListener %s" % listener)
         self.listeners.add(listener)
 
     def removeListener(self, listener):
@@ -250,11 +256,11 @@
         @raise ValueError: Raised when the given object is not a listener for
         this mailbox.
         """
-        log.debug('removeListener')
+        log.debug("removeListener")
         if listener in self.listeners:
             self.listeners.remove(listener)
         else:
-            raise imap4.MailboxException('Trying to remove an unknown listener')
+            raise imap4.MailboxException("Trying to remove an unknown listener")
 
     def addMessage(self, message, flags=(), date=None):
         """Add the given message to this mailbox.
@@ -265,7 +271,7 @@
         id if the message is added successfully and whose errback is
         invoked otherwise.
         """
-        log.debug('addMessage')
+        log.debug("addMessage")
         raise imap4.MailboxException("Client message addition not implemented yet")
 
     def expunge(self):
@@ -273,7 +279,7 @@
         @return: The list of message sequence numbers which were deleted,
         or a Deferred whose callback will be invoked with such a list.
         """
-        log.debug('expunge')
+        log.debug("expunge")
         self.mailbox.removeDeleted()
 
     def fetch(self, messages, uid):
@@ -282,16 +288,23 @@
         about
         @param uid: If true, the IDs specified in the query are UIDs;
         """
-        log.debug(u'fetch (%s, %s)' % (messages, uid))
+        log.debug(u"fetch (%s, %s)" % (messages, uid))
         if uid:
             messages.last = self.mailbox.getMaxUid()
             messages.getnext = self.mailbox.getNextExistingUid
             for mess_uid in messages:
                 if mess_uid is None:
-                    log.debug('stopping iteration')
+                    log.debug("stopping iteration")
                     raise StopIteration
                 try:
-                    yield (mess_uid, Message(mess_uid, self.mailbox.getFlagsUid(mess_uid), self.mailbox.getMessageUid(mess_uid)))
+                    yield (
+                        mess_uid,
+                        Message(
+                            mess_uid,
+                            self.mailbox.getFlagsUid(mess_uid),
+                            self.mailbox.getMessageUid(mess_uid),
+                        ),
+                    )
                 except IndexError:
                     continue
         else:
@@ -299,7 +312,14 @@
             for mess_idx in messages:
                 if mess_idx > self.getMessageCount():
                     raise StopIteration
-                yield (mess_idx, Message(mess_idx, self.mailbox.getFlags(mess_idx), self.mailbox.getMessage(mess_idx - 1)))
+                yield (
+                    mess_idx,
+                    Message(
+                        mess_idx,
+                        self.mailbox.getFlags(mess_idx),
+                        self.mailbox.getMessage(mess_idx - 1),
+                    ),
+                )
 
     def store(self, messages, flags, mode, uid):
         """Set the flags of one or more messages.
@@ -316,14 +336,16 @@
         been performed, or a Deferred whose callback will be invoked with
         such a dict.
         """
-        log.debug('store')
+        log.debug("store")
 
         flags = [flag.upper() for flag in flags]
 
         def updateFlags(getF, setF):
             ret = {}
             for mess_id in messages:
-                if (uid and mess_id is None) or (not uid and mess_id > self.getMessageCount()):
+                if (uid and mess_id is None) or (
+                    not uid and mess_id > self.getMessageCount()
+                ):
                     break
                 _flags = set(getF(mess_id) if mode else [])
                 if mode == -1:
@@ -348,7 +370,7 @@
             ret = updateFlags(self.mailbox.getFlags, self.mailbox.setFlags)
             newFlags = {}
             for idx in ret:
-                #we have to convert idx to uid for the listeners
+                # we have to convert idx to uid for the listeners
                 newFlags[self.mailbox.getUid(idx)] = ret[idx]
             for listener in self.listeners:
                 listener.flagsChanged(newFlags)
@@ -359,18 +381,24 @@
         Flags with the \\ prefix are reserved for use as system flags.
         @return: A list of the flags that can be set on messages in this mailbox.
         """
-        log.debug('getFlags')
-        return ['\\SEEN', '\\ANSWERED', '\\FLAGGED', '\\DELETED', '\\DRAFT']  # TODO: add '\\RECENT'
+        log.debug("getFlags")
+        return [
+            "\\SEEN",
+            "\\ANSWERED",
+            "\\FLAGGED",
+            "\\DELETED",
+            "\\DRAFT",
+        ]  # TODO: add '\\RECENT'
 
     def getHierarchicalDelimiter(self):
         """Get the character which delimits namespaces for in this mailbox.
         """
-        log.debug('getHierarchicalDelimiter')
-        return '.'
+        log.debug("getHierarchicalDelimiter")
+        return "."
 
 
 class ImapSatAccount(imap4.MemoryAccount):
-    #implements(imap4.IAccount)
+    # implements(imap4.IAccount)
 
     def __init__(self, host, profile):
         log.debug("ImapAccount init")
@@ -378,7 +406,7 @@
         self.profile = profile
         imap4.MemoryAccount.__init__(self, profile)
         self.addMailbox("Inbox")  # We only manage Inbox for the moment
-        log.debug('INBOX added')
+        log.debug("INBOX added")
 
     def _emptyMailbox(self, name, id):
         return SatMailbox(self.host, name, self.profile)
@@ -391,8 +419,8 @@
         self.host = host
 
     def requestAvatar(self, avatarID, mind, *interfaces):
-        log.debug('requestAvatar')
-        profile = avatarID.decode('utf-8')
+        log.debug("requestAvatar")
+        profile = avatarID.decode("utf-8")
         if imap4.IAccount not in interfaces:
             raise NotImplementedError
         return imap4.IAccount, ImapSatAccount(self.host, profile), lambda: None
@@ -404,16 +432,19 @@
     Check if the profile exists, and if the password is OK
     Return the profile as avatarId
     """
+
     implements(checkers.ICredentialsChecker)
-    credentialInterfaces = (credentials.IUsernamePassword,
-                            credentials.IUsernameHashedPassword)
+    credentialInterfaces = (
+        credentials.IUsernamePassword,
+        credentials.IUsernameHashedPassword,
+    )
 
     def __init__(self, host):
         self.host = host
 
     def _cbPasswordMatch(self, matched, profile):
         if matched:
-            return profile.encode('utf-8')
+            return profile.encode("utf-8")
         else:
             return failure.Failure(cred_error.UnauthorizedLogin())
 
@@ -421,7 +452,9 @@
         profiles = self.host.memory.getProfilesList()
         if not credentials.username in profiles:
             return defer.fail(cred_error.UnauthorizedLogin())
-        d = self.host.memory.asyncGetParamA("Password", "Connection", profile_key=credentials.username)
+        d = self.host.memory.asyncGetParamA(
+            "Password", "Connection", profile_key=credentials.username
+        )
         d.addCallback(lambda password: credentials.checkPassword(password))
         d.addCallback(self._cbPasswordMatch, credentials.username)
         return d
--- a/sat/plugins/plugin_misc_ip.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_misc_ip.py	Wed Jun 27 20:14:46 2018 +0200
@@ -20,6 +20,7 @@
 from sat.core.i18n import _, D_
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from sat.tools import xml_tools
 from twisted.web import client as webclient
@@ -34,10 +35,13 @@
 from twisted.words.protocols.jabber.xmlstream import XMPPHandler
 from twisted.words.protocols.jabber.error import StanzaError
 import urlparse
+
 try:
     import netifaces
 except ImportError:
-    log.warning(u"netifaces is not available, it help discovering IPs, you can install it on https://pypi.python.org/pypi/netifaces")
+    log.warning(
+        u"netifaces is not available, it help discovering IPs, you can install it on https://pypi.python.org/pypi/netifaces"
+    )
     netifaces = None
 
 
@@ -50,26 +54,29 @@
     C.PI_RECOMMENDATIONS: ["NAT-PORT"],
     C.PI_MAIN: "IPPlugin",
     C.PI_HANDLER: "yes",
-    C.PI_DESCRIPTION: _("""This plugin help to discover our external IP address.""")
+    C.PI_DESCRIPTION: _("""This plugin help to discover our external IP address."""),
 }
 
 # TODO: GET_IP_PAGE should be configurable in sat.conf
-GET_IP_PAGE = "http://salut-a-toi.org/whereami/" # This page must only return external IP of the requester
+GET_IP_PAGE = (
+    "http://salut-a-toi.org/whereami/"
+)  # This page must only return external IP of the requester
 GET_IP_LABEL = D_(u"Allow external get IP")
 GET_IP_CATEGORY = "General"
 GET_IP_NAME = "allow_get_ip"
 GET_IP_CONFIRM_TITLE = D_(u"Confirm external site request")
-GET_IP_CONFIRM = D_(u"""To facilitate data transfer, we need to contact a website.
+GET_IP_CONFIRM = D_(
+    u"""To facilitate data transfer, we need to contact a website.
 A request will be done on {page}
 That means that administrators of {domain} can know that you use "{app_name}" and your IP Address.
 
 IP address is an identifier to locate you on Internet (similar to a phone number).
 
 Do you agree to do this request ?
-""").format(
-    page = GET_IP_PAGE,
-    domain = urlparse.urlparse(GET_IP_PAGE).netloc,
-    app_name = C.APP_NAME)
+"""
+).format(
+    page=GET_IP_PAGE, domain=urlparse.urlparse(GET_IP_PAGE).netloc, app_name=C.APP_NAME
+)
 NS_IP_CHECK = "urn:xmpp:sic:1"
 
 PARAMS = """
@@ -80,7 +87,9 @@
     </category>
     </general>
     </params>
-    """.format(category=GET_IP_CATEGORY, name=GET_IP_NAME, label=GET_IP_LABEL)
+    """.format(
+    category=GET_IP_CATEGORY, name=GET_IP_NAME, label=GET_IP_LABEL
+)
 
 
 class IPPlugin(object):
@@ -94,7 +103,7 @@
 
         # NAT-Port
         try:
-            self._nat = host.plugins['NAT-PORT']
+            self._nat = host.plugins["NAT-PORT"]
         except KeyError:
             log.debug(u"NAT port plugin not available")
             self._nat = None
@@ -118,16 +127,26 @@
         if parameter is not set, a dialog is shown to use to get its confirmation, and parameted is set according to answer
         @return (defer.Deferred[bool]): True if external request is autorised
         """
-        allow_get_ip = self.host.memory.params.getParamA(GET_IP_NAME, GET_IP_CATEGORY, use_default=False)
+        allow_get_ip = self.host.memory.params.getParamA(
+            GET_IP_NAME, GET_IP_CATEGORY, use_default=False
+        )
 
         if allow_get_ip is None:
             # we don't have autorisation from user yet to use get_ip, we ask him
             def setParam(allowed):
                 # FIXME: we need to use boolConst as setParam only manage str/unicode
                 #        need to be fixed when params will be refactored
-                self.host.memory.setParam(GET_IP_NAME, C.boolConst(allowed), GET_IP_CATEGORY)
+                self.host.memory.setParam(
+                    GET_IP_NAME, C.boolConst(allowed), GET_IP_CATEGORY
+                )
                 return allowed
-            d = xml_tools.deferConfirm(self.host, _(GET_IP_CONFIRM), _(GET_IP_CONFIRM_TITLE), profile=client.profile)
+
+            d = xml_tools.deferConfirm(
+                self.host,
+                _(GET_IP_CONFIRM),
+                _(GET_IP_CONFIRM_TITLE),
+                profile=client.profile,
+            )
             d.addCallback(setParam)
             return d
 
@@ -140,7 +159,7 @@
         @param ip_addr(str): IP addresse
         @return (bool): True if addresse is acceptable
         """
-        return not ip_addr.startswith('127.')
+        return not ip_addr.startswith("127.")
 
     def _insertFirst(self, addresses, ip_addr):
         """Insert ip_addr as first item in addresses
@@ -164,9 +183,9 @@
         url = urlparse.urlparse(ext_url)
         port = url.port
         if port is None:
-            if url.scheme=='http':
+            if url.scheme == "http":
                 port = 80
-            elif url.scheme=='https':
+            elif url.scheme == "https":
                 port = 443
             else:
                 log.error(u"Unknown url scheme: {}".format(url.scheme))
@@ -175,6 +194,7 @@
             log.error(u"Can't find url hostname for {}".format(GET_IP_PAGE))
 
         point = endpoints.TCP4ClientEndpoint(reactor, url.hostname, port)
+
         def gotConnection(p):
             local_ip = p.transport.getHost().host
             p.transport.loseConnection()
@@ -196,7 +216,7 @@
         if self._local_ip_cache is not None:
             defer.returnValue(self._local_ip_cache)
         addresses = []
-        localhost = ['127.0.0.1']
+        localhost = ["127.0.0.1"]
 
         # we first try our luck with netifaces
         if netifaces is not None:
@@ -208,7 +228,7 @@
                 except KeyError:
                     continue
                 for data in inet_list:
-                    addresse = data['addr']
+                    addresse = data["addr"]
                     if self._filterAddresse(addresse):
                         addresses.append(addresse)
 
@@ -251,19 +271,20 @@
         if self._external_ip_cache is not None:
             defer.returnValue(self._external_ip_cache)
 
-
         # we first try with XEP-0279
         ip_check = yield self.host.hasFeature(client, NS_IP_CHECK)
         if ip_check:
             log.debug(u"Server IP Check available, we use it to retrieve our IP")
             iq_elt = client.IQ("get")
-            iq_elt.addElement((NS_IP_CHECK, 'address'))
+            iq_elt.addElement((NS_IP_CHECK, "address"))
             try:
                 result_elt = yield iq_elt.send()
-                address_elt = result_elt.elements(NS_IP_CHECK, 'address').next()
-                ip_elt = address_elt.elements(NS_IP_CHECK,'ip').next()
+                address_elt = result_elt.elements(NS_IP_CHECK, "address").next()
+                ip_elt = address_elt.elements(NS_IP_CHECK, "ip").next()
             except StopIteration:
-                log.warning(u"Server returned invalid result on XEP-0279 request, we ignore it")
+                log.warning(
+                    u"Server returned invalid result on XEP-0279 request, we ignore it"
+                )
             except StanzaError as e:
                 log.warning(u"error while requesting ip to server: {}".format(e))
             else:
@@ -289,7 +310,11 @@
             log.warning(u"Can't access Domain Name System")
             ip = None
         except web_error.Error as e:
-            log.warning(u"Error while retrieving IP on {url}: {message}".format(url=GET_IP_PAGE, message=e))
+            log.warning(
+                u"Error while retrieving IP on {url}: {message}".format(
+                    url=GET_IP_PAGE, message=e
+                )
+            )
             ip = None
         else:
             self._external_ip_cache = ip
@@ -299,8 +324,8 @@
 class IPPlugin_handler(XMPPHandler):
     implements(iwokkel.IDisco)
 
-    def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
+    def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
         return [disco.DiscoFeature(NS_IP_CHECK)]
 
-    def getDiscoItems(self, requestor, target, nodeIdentifier=''):
+    def getDiscoItems(self, requestor, target, nodeIdentifier=""):
         return []
--- a/sat/plugins/plugin_misc_nat-port.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_misc_nat-port.py	Wed Jun 27 20:14:46 2018 +0200
@@ -20,6 +20,7 @@
 from sat.core.i18n import _
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from sat.core import exceptions
 from twisted.internet import threads
@@ -30,7 +31,9 @@
 try:
     import miniupnpc
 except ImportError:
-    raise exceptions.MissingModule(u"Missing module MiniUPnPc, please download/install it (and its Python binding) at http://miniupnp.free.fr/ (or use pip install miniupnpc)")
+    raise exceptions.MissingModule(
+        u"Missing module MiniUPnPc, please download/install it (and its Python binding) at http://miniupnp.free.fr/ (or use pip install miniupnpc)"
+    )
 
 
 PLUGIN_INFO = {
@@ -42,8 +45,10 @@
     C.PI_DESCRIPTION: _("""Automatic NAT port mapping using UPnP"""),
 }
 
-STARTING_PORT = 6000 # starting point to automatically find a port
-DEFAULT_DESC = u'SaT port mapping' # we don't use "à" here as some bugged NAT don't manage charset correctly
+STARTING_PORT = 6000  # starting point to automatically find a port
+DEFAULT_DESC = (
+    u"SaT port mapping"
+)  # we don't use "à" here as some bugged NAT don't manage charset correctly
 
 
 class MappingError(Exception):
@@ -58,11 +63,11 @@
         self.host = host
         self._external_ip = None
         self._initialised = defer.Deferred()
-        self._upnp = miniupnpc.UPnP() # will be None if no device is available
-        self._upnp.discoverdelay=200
-        self._mutex = threading.Lock() # used to protect access to self._upnp
-        self._starting_port_cache = None # used to cache the first available port
-        self._to_unmap = [] # list of tuples (ext_port, protocol) of ports to unmap on unload
+        self._upnp = miniupnpc.UPnP()  # will be None if no device is available
+        self._upnp.discoverdelay = 200
+        self._mutex = threading.Lock()  # used to protect access to self._upnp
+        self._starting_port_cache = None  # used to cache the first available port
+        self._to_unmap = []  # list of tuples (ext_port, protocol) of ports to unmap on unload
         discover_d = threads.deferToThread(self._discover)
         discover_d.chainDeferred(self._initialised)
         self._initialised.addErrback(self._init_failed)
@@ -99,12 +104,14 @@
         @param local(bool): True to get external IP address, False to get local network one
         @return (None, str): found IP address, or None of something got wrong
         """
+
         def getIP(dummy):
             if self._upnp is None:
                 return None
             # lanaddr can be the empty string if not found,
             # we need to return None in this case
             return (self._upnp.lanaddr or None) if local else self._external_ip
+
         return self._initialised.addCallback(getIP)
 
     def _unmapPortsBlocking(self):
@@ -115,11 +122,17 @@
                 log.info(u"Unmapping port {}".format(port))
                 unmapping = self._upnp.deleteportmapping(
                     # the last parameter is remoteHost, we don't use it
-                    port, protocol, '')
+                    port,
+                    protocol,
+                    "",
+                )
 
                 if not unmapping:
-                    log.error(u"Can't unmap port {port} ({protocol})".format(
-                        port=port, protocol=protocol))
+                    log.error(
+                        u"Can't unmap port {port} ({protocol})".format(
+                            port=port, protocol=protocol
+                        )
+                    )
             del self._to_unmap[:]
         finally:
             self._mutex.release()
@@ -143,8 +156,8 @@
                 ext_port = STARTING_PORT if starting_port is None else starting_port
                 ret = self._upnp.getspecificportmapping(ext_port, protocol)
                 while ret != None and ext_port < 65536:
-                        ext_port += 1
-                        ret = self._upnp.getspecificportmapping(ext_port, protocol)
+                    ext_port += 1
+                    ret = self._upnp.getspecificportmapping(ext_port, protocol)
                 if starting_port is None:
                     # XXX: we cache the first successfuly found external port
                     #      to avoid testing again the first series the next time
@@ -153,7 +166,13 @@
             try:
                 mapping = self._upnp.addportmapping(
                     # the last parameter is remoteHost, we don't use it
-                    ext_port, protocol, self._upnp.lanaddr, int_port, desc, '')
+                    ext_port,
+                    protocol,
+                    self._upnp.lanaddr,
+                    int_port,
+                    desc,
+                    "",
+                )
             except Exception as e:
                 log.error(_(u"addportmapping error: {msg}").format(msg=e))
                 raise failure.Failure(MappingError())
@@ -167,7 +186,7 @@
 
         return ext_port
 
-    def mapPort(self, int_port, ext_port=None, protocol='TCP', desc=DEFAULT_DESC):
+    def mapPort(self, int_port, ext_port=None, protocol="TCP", desc=DEFAULT_DESC):
         """Add a port mapping
 
         @param int_port(int): internal port to use
@@ -179,19 +198,25 @@
         """
         if self._upnp is None:
             return defer.succeed(None)
+
         def mappingCb(ext_port):
-            log.info(u"{protocol} mapping from {int_port} to {ext_port} successful".format(
-                protocol = protocol,
-                int_port = int_port,
-                ext_port = ext_port,
-                ))
+            log.info(
+                u"{protocol} mapping from {int_port} to {ext_port} successful".format(
+                    protocol=protocol, int_port=int_port, ext_port=ext_port
+                )
+            )
             return ext_port
+
         def mappingEb(failure_):
             failure_.trap(MappingError)
             log.warning(u"Can't map internal {int_port}".format(int_port=int_port))
+
         def mappingUnknownEb(failure_):
             log.error(_(u"error while trying to map ports: {msg}").format(msg=failure_))
-        d = threads.deferToThread(self._mapPortBlocking, int_port, ext_port, protocol, desc)
+
+        d = threads.deferToThread(
+            self._mapPortBlocking, int_port, ext_port, protocol, desc
+        )
         d.addCallbacks(mappingCb, mappingEb)
         d.addErrback(mappingUnknownEb)
         return d
--- a/sat/plugins/plugin_misc_quiz.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_misc_quiz.py	Wed Jun 27 20:14:46 2018 +0200
@@ -20,6 +20,7 @@
 from sat.core.i18n import _
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from twisted.words.xish import domish
 from twisted.internet import reactor
@@ -27,8 +28,8 @@
 from time import time
 
 
-NS_QG = 'http://www.goffi.org/protocol/quiz'
-QG_TAG = 'quiz'
+NS_QG = "http://www.goffi.org/protocol/quiz"
+QG_TAG = "quiz"
 
 PLUGIN_INFO = {
     C.PI_NAME: "Quiz game plugin",
@@ -38,78 +39,147 @@
     C.PI_DEPENDENCIES: ["XEP-0045", "XEP-0249", "ROOM-GAME"],
     C.PI_MAIN: "Quiz",
     C.PI_HANDLER: "yes",
-    C.PI_DESCRIPTION: _("""Implementation of Quiz game""")
+    C.PI_DESCRIPTION: _("""Implementation of Quiz game"""),
 }
 
 
 class Quiz(object):
-
     def inheritFromRoomGame(self, host):
         global RoomGame
         RoomGame = host.plugins["ROOM-GAME"].__class__
-        self.__class__ = type(self.__class__.__name__, (self.__class__, RoomGame, object), {})
+        self.__class__ = type(
+            self.__class__.__name__, (self.__class__, RoomGame, object), {}
+        )
 
     def __init__(self, host):
         log.info(_("Plugin Quiz initialization"))
         self.inheritFromRoomGame(host)
-        RoomGame._init_(self, host, PLUGIN_INFO, (NS_QG, QG_TAG), game_init={'stage': None}, player_init={'score': 0})
-        host.bridge.addMethod("quizGameLaunch", ".plugin", in_sign='asss', out_sign='', method=self._prepareRoom)  # args: players, room_jid, profile
-        host.bridge.addMethod("quizGameCreate", ".plugin", in_sign='sass', out_sign='', method=self._createGame)  # args: room_jid, players, profile
-        host.bridge.addMethod("quizGameReady", ".plugin", in_sign='sss', out_sign='', method=self._playerReady)  # args: player, referee, profile
-        host.bridge.addMethod("quizGameAnswer", ".plugin", in_sign='ssss', out_sign='', method=self.playerAnswer)
-        host.bridge.addSignal("quizGameStarted", ".plugin", signature='ssass')  # args: room_jid, referee, players, profile
-        host.bridge.addSignal("quizGameNew", ".plugin",
-                              signature='sa{ss}s',
-                              doc={'summary': 'Start a new game',
-                                   'param_0': "room_jid: jid of game's room",
-                                   'param_1': "game_data: data of the game",
-                                   'param_2': '%(doc_profile)s'})
-        host.bridge.addSignal("quizGameQuestion", ".plugin",
-                              signature='sssis',
-                              doc={'summary': "Send the current question",
-                                   'param_0': "room_jid: jid of game's room",
-                                   'param_1': "question_id: question id",
-                                   'param_2': "question: question to ask",
-                                   'param_3': "timer: timer",
-                                   'param_4': '%(doc_profile)s'})
-        host.bridge.addSignal("quizGamePlayerBuzzed", ".plugin",
-                              signature='ssbs',
-                              doc={'summary': "A player just pressed the buzzer",
-                                   'param_0': "room_jid: jid of game's room",
-                                   'param_1': "player: player who pushed the buzzer",
-                                   'param_2': "pause: should the game be paused ?",
-                                   'param_3': '%(doc_profile)s'})
-        host.bridge.addSignal("quizGamePlayerSays", ".plugin",
-                              signature='sssis',
-                              doc={'summary': "A player just pressed the buzzer",
-                                   'param_0': "room_jid: jid of game's room",
-                                   'param_1': "player: player who pushed the buzzer",
-                                   'param_2': "text: what the player say",
-                                   'param_3': "delay: how long, in seconds, the text must appear",
-                                   'param_4': '%(doc_profile)s'})
-        host.bridge.addSignal("quizGameAnswerResult", ".plugin",
-                              signature='ssba{si}s',
-                              doc={'summary': "Result of the just given answer",
-                                   'param_0': "room_jid: jid of game's room",
-                                   'param_1': "player: player who gave the answer",
-                                   'param_2': "good_answer: True if the answer is right",
-                                   'param_3': "score: dict of score with player as key",
-                                   'param_4': '%(doc_profile)s'})
-        host.bridge.addSignal("quizGameTimerExpired", ".plugin",
-                              signature='ss',
-                              doc={'summary': "Nobody answered the question in time",
-                                   'param_0': "room_jid: jid of game's room",
-                                   'param_1': '%(doc_profile)s'})
-        host.bridge.addSignal("quizGameTimerRestarted", ".plugin",
-                              signature='sis',
-                              doc={'summary': "Nobody answered the question in time",
-                                   'param_0': "room_jid: jid of game's room",
-                                   'param_1': "time_left: time left before timer expiration",
-                                   'param_2': '%(doc_profile)s'})
+        RoomGame._init_(
+            self,
+            host,
+            PLUGIN_INFO,
+            (NS_QG, QG_TAG),
+            game_init={"stage": None},
+            player_init={"score": 0},
+        )
+        host.bridge.addMethod(
+            "quizGameLaunch",
+            ".plugin",
+            in_sign="asss",
+            out_sign="",
+            method=self._prepareRoom,
+        )  # args: players, room_jid, profile
+        host.bridge.addMethod(
+            "quizGameCreate",
+            ".plugin",
+            in_sign="sass",
+            out_sign="",
+            method=self._createGame,
+        )  # args: room_jid, players, profile
+        host.bridge.addMethod(
+            "quizGameReady",
+            ".plugin",
+            in_sign="sss",
+            out_sign="",
+            method=self._playerReady,
+        )  # args: player, referee, profile
+        host.bridge.addMethod(
+            "quizGameAnswer",
+            ".plugin",
+            in_sign="ssss",
+            out_sign="",
+            method=self.playerAnswer,
+        )
+        host.bridge.addSignal(
+            "quizGameStarted", ".plugin", signature="ssass"
+        )  # args: room_jid, referee, players, profile
+        host.bridge.addSignal(
+            "quizGameNew",
+            ".plugin",
+            signature="sa{ss}s",
+            doc={
+                "summary": "Start a new game",
+                "param_0": "room_jid: jid of game's room",
+                "param_1": "game_data: data of the game",
+                "param_2": "%(doc_profile)s",
+            },
+        )
+        host.bridge.addSignal(
+            "quizGameQuestion",
+            ".plugin",
+            signature="sssis",
+            doc={
+                "summary": "Send the current question",
+                "param_0": "room_jid: jid of game's room",
+                "param_1": "question_id: question id",
+                "param_2": "question: question to ask",
+                "param_3": "timer: timer",
+                "param_4": "%(doc_profile)s",
+            },
+        )
+        host.bridge.addSignal(
+            "quizGamePlayerBuzzed",
+            ".plugin",
+            signature="ssbs",
+            doc={
+                "summary": "A player just pressed the buzzer",
+                "param_0": "room_jid: jid of game's room",
+                "param_1": "player: player who pushed the buzzer",
+                "param_2": "pause: should the game be paused ?",
+                "param_3": "%(doc_profile)s",
+            },
+        )
+        host.bridge.addSignal(
+            "quizGamePlayerSays",
+            ".plugin",
+            signature="sssis",
+            doc={
+                "summary": "A player just pressed the buzzer",
+                "param_0": "room_jid: jid of game's room",
+                "param_1": "player: player who pushed the buzzer",
+                "param_2": "text: what the player say",
+                "param_3": "delay: how long, in seconds, the text must appear",
+                "param_4": "%(doc_profile)s",
+            },
+        )
+        host.bridge.addSignal(
+            "quizGameAnswerResult",
+            ".plugin",
+            signature="ssba{si}s",
+            doc={
+                "summary": "Result of the just given answer",
+                "param_0": "room_jid: jid of game's room",
+                "param_1": "player: player who gave the answer",
+                "param_2": "good_answer: True if the answer is right",
+                "param_3": "score: dict of score with player as key",
+                "param_4": "%(doc_profile)s",
+            },
+        )
+        host.bridge.addSignal(
+            "quizGameTimerExpired",
+            ".plugin",
+            signature="ss",
+            doc={
+                "summary": "Nobody answered the question in time",
+                "param_0": "room_jid: jid of game's room",
+                "param_1": "%(doc_profile)s",
+            },
+        )
+        host.bridge.addSignal(
+            "quizGameTimerRestarted",
+            ".plugin",
+            signature="sis",
+            doc={
+                "summary": "Nobody answered the question in time",
+                "param_0": "room_jid: jid of game's room",
+                "param_1": "time_left: time left before timer expiration",
+                "param_2": "%(doc_profile)s",
+            },
+        )
 
     def __game_data_to_xml(self, game_data):
         """Convert a game data dict to domish element"""
-        game_data_elt = domish.Element((None, 'game_data'))
+        game_data_elt = domish.Element((None, "game_data"))
         for data in game_data:
             data_elt = domish.Element((None, data))
             data_elt.addContent(game_data[data])
@@ -129,57 +199,68 @@
         @return: (player, good_answer, score)"""
         score = {}
         for score_elt in answer_result_elt.elements():
-            score[score_elt['player']] = int(score_elt['score'])
-        return (answer_result_elt['player'], answer_result_elt['good_answer'] == str(True), score)
+            score[score_elt["player"]] = int(score_elt["score"])
+        return (
+            answer_result_elt["player"],
+            answer_result_elt["good_answer"] == str(True),
+            score,
+        )
 
     def __answer_result(self, player_answering, good_answer, game_data):
         """Convert a domish an answer_result element
         @param player_answering: player who gave the answer
         @param good_answer: True is the answer is right
         @param game_data: data of the game"""
-        players_data = game_data['players_data']
+        players_data = game_data["players_data"]
         score = {}
-        for player in game_data['players']:
-            score[player] = players_data[player]['score']
+        for player in game_data["players"]:
+            score[player] = players_data[player]["score"]
 
-        answer_result_elt = domish.Element((None, 'answer_result'))
-        answer_result_elt['player'] = player_answering
-        answer_result_elt['good_answer'] = str(good_answer)
+        answer_result_elt = domish.Element((None, "answer_result"))
+        answer_result_elt["player"] = player_answering
+        answer_result_elt["good_answer"] = str(good_answer)
 
         for player in score:
             score_elt = domish.Element((None, "score"))
-            score_elt['player'] = player
-            score_elt['score'] = str(score[player])
+            score_elt["player"] = player
+            score_elt["score"] = str(score[player])
             answer_result_elt.addChild(score_elt)
 
         return answer_result_elt
 
     def __ask_question(self, question_id, question, timer):
         """Create a element for asking a question"""
-        question_elt = domish.Element((None, 'question'))
-        question_elt['id'] = question_id
-        question_elt['timer'] = str(timer)
+        question_elt = domish.Element((None, "question"))
+        question_elt["id"] = question_id
+        question_elt["timer"] = str(timer)
         question_elt.addContent(question)
         return question_elt
 
     def __start_play(self, room_jid, game_data, profile):
         """Start the game (tell to the first player after dealer to play"""
         client = self.host.getClient(profile)
-        game_data['stage'] = "play"
-        next_player_idx = game_data['current_player'] = (game_data['init_player'] + 1) % len(game_data['players'])  # the player after the dealer start
-        game_data['first_player'] = next_player = game_data['players'][next_player_idx]
+        game_data["stage"] = "play"
+        next_player_idx = game_data["current_player"] = (
+            game_data["init_player"] + 1
+        ) % len(
+            game_data["players"]
+        )  # the player after the dealer start
+        game_data["first_player"] = next_player = game_data["players"][next_player_idx]
         to_jid = jid.JID(room_jid.userhost() + "/" + next_player)
         mess = self.createGameElt(to_jid)
-        mess.firstChildElement().addElement('your_turn')
+        mess.firstChildElement().addElement("your_turn")
         client.send(mess)
 
     def playerAnswer(self, player, referee, answer, profile_key=C.PROF_KEY_NONE):
         """Called when a player give an answer"""
         client = self.host.getClient(profile_key)
-        log.debug(u'new player answer (%(profile)s): %(answer)s' % {'profile': client.profile, 'answer': answer})
+        log.debug(
+            u"new player answer (%(profile)s): %(answer)s"
+            % {"profile": client.profile, "answer": answer}
+        )
         mess = self.createGameElt(jid.JID(referee))
-        answer_elt = mess.firstChildElement().addElement('player_answer')
-        answer_elt['player'] = player
+        answer_elt = mess.firstChildElement().addElement("player_answer")
+        answer_elt["player"] = player
         answer_elt.addContent(answer)
         client.send(mess)
 
@@ -187,9 +268,9 @@
         """Called when nobody answered the question in time"""
         client = self.host.getClient(profile)
         game_data = self.games[room_jid]
-        game_data['stage'] = 'expired'
+        game_data["stage"] = "expired"
         mess = self.createGameElt(room_jid)
-        mess.firstChildElement().addElement('timer_expired')
+        mess.firstChildElement().addElement("timer_expired")
         client.send(mess)
         reactor.callLater(4, self.askQuestion, room_jid, client.profile)
 
@@ -197,49 +278,59 @@
         """Stop the timer and save the time left"""
         game_data = self.games[room_jid]
         left = max(0, game_data["timer"].getTime() - time())
-        game_data['timer'].cancel()
-        game_data['time_left'] = int(left)
-        game_data['previous_stage'] = game_data['stage']
-        game_data['stage'] = "paused"
+        game_data["timer"].cancel()
+        game_data["time_left"] = int(left)
+        game_data["previous_stage"] = game_data["stage"]
+        game_data["stage"] = "paused"
 
     def restartTimer(self, room_jid, profile):
         """Restart a timer with the saved time"""
         client = self.host.getClient(profile)
         game_data = self.games[room_jid]
-        assert game_data['time_left'] is not None
+        assert game_data["time_left"] is not None
         mess = self.createGameElt(room_jid)
-        mess.firstChildElement().addElement('timer_restarted')
-        jabber_client.restarted_elt["time_left"] = str(game_data['time_left'])
+        mess.firstChildElement().addElement("timer_restarted")
+        jabber_client.restarted_elt["time_left"] = str(game_data["time_left"])
         client.send(mess)
-        game_data["timer"] = reactor.callLater(game_data['time_left'], self.timerExpired, room_jid, profile)
+        game_data["timer"] = reactor.callLater(
+            game_data["time_left"], self.timerExpired, room_jid, profile
+        )
         game_data["time_left"] = None
-        game_data['stage'] = game_data['previous_stage']
-        del game_data['previous_stage']
+        game_data["stage"] = game_data["previous_stage"]
+        del game_data["previous_stage"]
 
     def askQuestion(self, room_jid, profile):
         """Ask a new question"""
         client = self.host.getClient(profile)
         game_data = self.games[room_jid]
-        game_data['stage'] = "question"
-        game_data['question_id'] = "1"
+        game_data["stage"] = "question"
+        game_data["question_id"] = "1"
         timer = 30
         mess = self.createGameElt(room_jid)
-        mess.firstChildElement().addChild(self.__ask_question(game_data['question_id'], u"Quel est l'âge du capitaine ?", timer))
+        mess.firstChildElement().addChild(
+            self.__ask_question(
+                game_data["question_id"], u"Quel est l'âge du capitaine ?", timer
+            )
+        )
         client.send(mess)
-        game_data["timer"] = reactor.callLater(timer, self.timerExpired, room_jid, profile)
+        game_data["timer"] = reactor.callLater(
+            timer, self.timerExpired, room_jid, profile
+        )
         game_data["time_left"] = None
 
     def checkAnswer(self, room_jid, player, answer, profile):
         """Check if the answer given is right"""
         client = self.host.getClient(profile)
         game_data = self.games[room_jid]
-        players_data = game_data['players_data']
-        good_answer = game_data['question_id'] == "1" and answer == "42"
-        players_data[player]['score'] += 1 if good_answer else -1
-        players_data[player]['score'] = min(9, max(0, players_data[player]['score']))
+        players_data = game_data["players_data"]
+        good_answer = game_data["question_id"] == "1" and answer == "42"
+        players_data[player]["score"] += 1 if good_answer else -1
+        players_data[player]["score"] = min(9, max(0, players_data[player]["score"]))
 
         mess = self.createGameElt(room_jid)
-        mess.firstChildElement().addChild(self.__answer_result(player, good_answer, game_data))
+        mess.firstChildElement().addChild(
+            self.__answer_result(player, good_answer, game_data)
+        )
         client.send(mess)
 
         if good_answer:
@@ -249,82 +340,117 @@
 
     def newGame(self, room_jid, profile):
         """Launch a new round"""
-        common_data = {'game_score': 0}
-        new_game_data = {"instructions": _(u"""Bienvenue dans cette partie rapide de quizz, le premier à atteindre le score de 9 remporte le jeu
+        common_data = {"game_score": 0}
+        new_game_data = {
+            "instructions": _(
+                u"""Bienvenue dans cette partie rapide de quizz, le premier à atteindre le score de 9 remporte le jeu
 
-Attention, tu es prêt ?""")}
+Attention, tu es prêt ?"""
+            )
+        }
         msg_elts = self.__game_data_to_xml(new_game_data)
         RoomGame.newRound(self, room_jid, (common_data, msg_elts), profile)
         reactor.callLater(10, self.askQuestion, room_jid, profile)
 
     def room_game_cmd(self, mess_elt, profile):
         client = self.host.getClient(profile)
-        from_jid = jid.JID(mess_elt['from'])
+        from_jid = jid.JID(mess_elt["from"])
         room_jid = jid.JID(from_jid.userhost())
         game_elt = mess_elt.firstChildElement()
         game_data = self.games[room_jid]
-        # if 'players_data' in game_data:
-        #     players_data = game_data['players_data']
+        #  if 'players_data' in game_data:
+        #      players_data = game_data['players_data']
 
         for elt in game_elt.elements():
 
-            if elt.name == 'started':  # new game created
+            if elt.name == "started":  # new game created
                 players = []
                 for player in elt.elements():
                     players.append(unicode(player))
-                self.host.bridge.quizGameStarted(room_jid.userhost(), from_jid.full(), players, profile)
+                self.host.bridge.quizGameStarted(
+                    room_jid.userhost(), from_jid.full(), players, profile
+                )
 
-            elif elt.name == 'player_ready':  # ready to play
-                player = elt['player']
-                status = self.games[room_jid]['status']
-                nb_players = len(self.games[room_jid]['players'])
-                status[player] = 'ready'
-                log.debug(_(u'Player %(player)s is ready to start [status: %(status)s]') % {'player': player, 'status': status})
-                if status.values().count('ready') == nb_players:  # everybody is ready, we can start the game
+            elif elt.name == "player_ready":  # ready to play
+                player = elt["player"]
+                status = self.games[room_jid]["status"]
+                nb_players = len(self.games[room_jid]["players"])
+                status[player] = "ready"
+                log.debug(
+                    _(u"Player %(player)s is ready to start [status: %(status)s]")
+                    % {"player": player, "status": status}
+                )
+                if (
+                    status.values().count("ready") == nb_players
+                ):  # everybody is ready, we can start the game
                     self.newGame(room_jid, profile)
 
-            elif elt.name == 'game_data':
-                self.host.bridge.quizGameNew(room_jid.userhost(), self.__xml_to_game_data(elt), profile)
+            elif elt.name == "game_data":
+                self.host.bridge.quizGameNew(
+                    room_jid.userhost(), self.__xml_to_game_data(elt), profile
+                )
 
-            elif elt.name == 'question':  # A question is asked
-                self.host.bridge.quizGameQuestion(room_jid.userhost(), elt["id"], unicode(elt), int(elt["timer"]), profile)
+            elif elt.name == "question":  # A question is asked
+                self.host.bridge.quizGameQuestion(
+                    room_jid.userhost(),
+                    elt["id"],
+                    unicode(elt),
+                    int(elt["timer"]),
+                    profile,
+                )
 
-            elif elt.name == 'player_answer':
-                player = elt['player']
-                pause = game_data['stage'] == 'question'  # we pause the game only if we are have a question at the moment
+            elif elt.name == "player_answer":
+                player = elt["player"]
+                pause = (
+                    game_data["stage"] == "question"
+                )  # we pause the game only if we are have a question at the moment
                 # we first send a buzzer message
                 mess = self.createGameElt(room_jid)
-                buzzer_elt = mess.firstChildElement().addElement('player_buzzed')
-                buzzer_elt['player'] = player
-                buzzer_elt['pause'] = str(pause)
+                buzzer_elt = mess.firstChildElement().addElement("player_buzzed")
+                buzzer_elt["player"] = player
+                buzzer_elt["pause"] = str(pause)
                 client.send(mess)
                 if pause:
                     self.pauseTimer(room_jid)
                     # and we send the player answer
                     mess = self.createGameElt(room_jid)
                     _answer = unicode(elt)
-                    say_elt = mess.firstChildElement().addElement('player_says')
-                    say_elt['player'] = player
+                    say_elt = mess.firstChildElement().addElement("player_says")
+                    say_elt["player"] = player
                     say_elt.addContent(_answer)
-                    say_elt['delay'] = "3"
+                    say_elt["delay"] = "3"
                     reactor.callLater(2, client.send, mess)
-                    reactor.callLater(6, self.checkAnswer, room_jid, player, _answer, profile=profile)
+                    reactor.callLater(
+                        6, self.checkAnswer, room_jid, player, _answer, profile=profile
+                    )
+
+            elif elt.name == "player_buzzed":
+                self.host.bridge.quizGamePlayerBuzzed(
+                    room_jid.userhost(), elt["player"], elt["pause"] == str(True), profile
+                )
 
-            elif elt.name == 'player_buzzed':
-                self.host.bridge.quizGamePlayerBuzzed(room_jid.userhost(), elt["player"], elt['pause'] == str(True), profile)
-
-            elif elt.name == 'player_says':
-                self.host.bridge.quizGamePlayerSays(room_jid.userhost(), elt["player"], unicode(elt), int(elt["delay"]), profile)
+            elif elt.name == "player_says":
+                self.host.bridge.quizGamePlayerSays(
+                    room_jid.userhost(),
+                    elt["player"],
+                    unicode(elt),
+                    int(elt["delay"]),
+                    profile,
+                )
 
-            elif elt.name == 'answer_result':
+            elif elt.name == "answer_result":
                 player, good_answer, score = self.__answer_result_to_signal_args(elt)
-                self.host.bridge.quizGameAnswerResult(room_jid.userhost(), player, good_answer, score, profile)
+                self.host.bridge.quizGameAnswerResult(
+                    room_jid.userhost(), player, good_answer, score, profile
+                )
 
-            elif elt.name == 'timer_expired':
+            elif elt.name == "timer_expired":
                 self.host.bridge.quizGameTimerExpired(room_jid.userhost(), profile)
 
-            elif elt.name == 'timer_restarted':
-                self.host.bridge.quizGameTimerRestarted(room_jid.userhost(), int(elt['time_left']), profile)
+            elif elt.name == "timer_restarted":
+                self.host.bridge.quizGameTimerRestarted(
+                    room_jid.userhost(), int(elt["time_left"]), profile
+                )
 
             else:
-                log.error(_(u'Unmanaged game element: %s') % elt.name)
+                log.error(_(u"Unmanaged game element: %s") % elt.name)
--- a/sat/plugins/plugin_misc_radiocol.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_misc_radiocol.py	Wed Jun 27 20:14:46 2018 +0200
@@ -20,6 +20,7 @@
 from sat.core.i18n import _, D_
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from twisted.words.xish import domish
 from twisted.internet import reactor
@@ -30,17 +31,20 @@
 import copy
 import time
 from os import unlink
+
 try:
     from mutagen.oggvorbis import OggVorbis, OggVorbisHeaderError
     from mutagen.mp3 import MP3, HeaderNotFoundError
     from mutagen.easyid3 import EasyID3
     from mutagen.id3 import ID3NoHeaderError
 except ImportError:
-    raise exceptions.MissingModule(u"Missing module Mutagen, please download/install from https://bitbucket.org/lazka/mutagen")
+    raise exceptions.MissingModule(
+        u"Missing module Mutagen, please download/install from https://bitbucket.org/lazka/mutagen"
+    )
 
 
-NC_RADIOCOL = 'http://www.goffi.org/protocol/radiocol'
-RADIOC_TAG = 'radiocol'
+NC_RADIOCOL = "http://www.goffi.org/protocol/radiocol"
+RADIOC_TAG = "radiocol"
 
 PLUGIN_INFO = {
     C.PI_NAME: "Radio collective plugin",
@@ -50,7 +54,7 @@
     C.PI_DEPENDENCIES: ["XEP-0045", "XEP-0249", "ROOM-GAME"],
     C.PI_MAIN: "Radiocol",
     C.PI_HANDLER: "yes",
-    C.PI_DESCRIPTION: _("""Implementation of radio collective""")
+    C.PI_DESCRIPTION: _("""Implementation of radio collective"""),
 }
 
 
@@ -61,34 +65,80 @@
 
 
 class Radiocol(object):
-
     def inheritFromRoomGame(self, host):
         global RoomGame
         RoomGame = host.plugins["ROOM-GAME"].__class__
-        self.__class__ = type(self.__class__.__name__, (self.__class__, RoomGame, object), {})
+        self.__class__ = type(
+            self.__class__.__name__, (self.__class__, RoomGame, object), {}
+        )
 
     def __init__(self, host):
         log.info(_("Radio collective initialization"))
         self.inheritFromRoomGame(host)
-        RoomGame._init_(self, host, PLUGIN_INFO, (NC_RADIOCOL, RADIOC_TAG),
-                        game_init={'queue': [], 'upload': True, 'playing': None, 'playing_time': 0, 'to_delete': {}})
+        RoomGame._init_(
+            self,
+            host,
+            PLUGIN_INFO,
+            (NC_RADIOCOL, RADIOC_TAG),
+            game_init={
+                "queue": [],
+                "upload": True,
+                "playing": None,
+                "playing_time": 0,
+                "to_delete": {},
+            },
+        )
         self.host = host
-        host.bridge.addMethod("radiocolLaunch", ".plugin", in_sign='asss', out_sign='', method=self._prepareRoom, async=True)
-        host.bridge.addMethod("radiocolCreate", ".plugin", in_sign='sass', out_sign='', method=self._createGame)
-        host.bridge.addMethod("radiocolSongAdded", ".plugin", in_sign='sss', out_sign='', method=self._radiocolSongAdded, async=True)
-        host.bridge.addSignal("radiocolPlayers", ".plugin", signature='ssass')  # room_jid, referee, players, profile
-        host.bridge.addSignal("radiocolStarted", ".plugin", signature='ssasais')  # room_jid, referee, players, [QUEUE_TO_START, QUEUE_LIMIT], profile
-        host.bridge.addSignal("radiocolSongRejected", ".plugin", signature='sss')  # room_jid, reason, profile
-        host.bridge.addSignal("radiocolPreload", ".plugin", signature='ssssssss')  # room_jid, timestamp, filename, title, artist, album, profile
-        host.bridge.addSignal("radiocolPlay", ".plugin", signature='sss')  # room_jid, filename, profile
-        host.bridge.addSignal("radiocolNoUpload", ".plugin", signature='ss')  # room_jid, profile
-        host.bridge.addSignal("radiocolUploadOk", ".plugin", signature='ss')  # room_jid, profile
+        host.bridge.addMethod(
+            "radiocolLaunch",
+            ".plugin",
+            in_sign="asss",
+            out_sign="",
+            method=self._prepareRoom,
+            async=True,
+        )
+        host.bridge.addMethod(
+            "radiocolCreate",
+            ".plugin",
+            in_sign="sass",
+            out_sign="",
+            method=self._createGame,
+        )
+        host.bridge.addMethod(
+            "radiocolSongAdded",
+            ".plugin",
+            in_sign="sss",
+            out_sign="",
+            method=self._radiocolSongAdded,
+            async=True,
+        )
+        host.bridge.addSignal(
+            "radiocolPlayers", ".plugin", signature="ssass"
+        )  # room_jid, referee, players, profile
+        host.bridge.addSignal(
+            "radiocolStarted", ".plugin", signature="ssasais"
+        )  # room_jid, referee, players, [QUEUE_TO_START, QUEUE_LIMIT], profile
+        host.bridge.addSignal(
+            "radiocolSongRejected", ".plugin", signature="sss"
+        )  # room_jid, reason, profile
+        host.bridge.addSignal(
+            "radiocolPreload", ".plugin", signature="ssssssss"
+        )  # room_jid, timestamp, filename, title, artist, album, profile
+        host.bridge.addSignal(
+            "radiocolPlay", ".plugin", signature="sss"
+        )  # room_jid, filename, profile
+        host.bridge.addSignal(
+            "radiocolNoUpload", ".plugin", signature="ss"
+        )  # room_jid, profile
+        host.bridge.addSignal(
+            "radiocolUploadOk", ".plugin", signature="ss"
+        )  # room_jid, profile
 
     def __create_preload_elt(self, sender, song_added_elt):
         preload_elt = copy.deepcopy(song_added_elt)
-        preload_elt.name = 'preload'
-        preload_elt['sender'] = sender
-        preload_elt['timestamp'] = str(time.time())
+        preload_elt.name = "preload"
+        preload_elt["sender"] = sender
+        preload_elt["timestamp"] = str(time.time())
         # attributes filename, title, artist, album, length have been copied
         # XXX: the frontend should know the temporary directory where file is put
         return preload_elt
@@ -108,7 +158,7 @@
         #     Here we cheat because we know we are on the same host, and we don't
         #     check data. Referee will have to parse the song himself to check it
         try:
-            if song_path.lower().endswith('.mp3'):
+            if song_path.lower().endswith(".mp3"):
                 actual_song = MP3(song_path)
                 try:
                     song = EasyID3(song_path)
@@ -116,6 +166,7 @@
                     class Info(object):
                         def __init__(self, length):
                             self.length = length
+
                     song.info = Info(actual_song.info.length)
                 except ID3NoHeaderError:
                     song = actual_song
@@ -124,17 +175,30 @@
         except (OggVorbisHeaderError, HeaderNotFoundError):
             # this file is not ogg vorbis nor mp3, we reject it
             self.deleteFile(song_path)  # FIXME: same host trick (see note above)
-            return defer.fail(exceptions.DataError(D_("The uploaded file has been rejected, only Ogg Vorbis and MP3 songs are accepted.")))
+            return defer.fail(
+                exceptions.DataError(
+                    D_(
+                        "The uploaded file has been rejected, only Ogg Vorbis and MP3 songs are accepted."
+                    )
+                )
+            )
 
-        attrs = {'filename': os.path.basename(song_path),
-                 'title': song.get("title", ["Unknown"])[0],
-                 'artist': song.get("artist", ["Unknown"])[0],
-                 'album': song.get("album", ["Unknown"])[0],
-                 'length': str(song.info.length)
-                 }
-        radio_data = self.games[referee.userhostJID()]  # FIXME: referee comes from Libervia's client side, it's unsecure
-        radio_data['to_delete'][attrs['filename']] = song_path  # FIXME: works only because of the same host trick, see the note under the docstring
-        return self.send(referee, ('', 'song_added'), attrs, profile=profile)
+        attrs = {
+            "filename": os.path.basename(song_path),
+            "title": song.get("title", ["Unknown"])[0],
+            "artist": song.get("artist", ["Unknown"])[0],
+            "album": song.get("album", ["Unknown"])[0],
+            "length": str(song.info.length),
+        }
+        radio_data = self.games[
+            referee.userhostJID()
+        ]  # FIXME: referee comes from Libervia's client side, it's unsecure
+        radio_data["to_delete"][
+            attrs["filename"]
+        ] = (
+            song_path
+        )  # FIXME: works only because of the same host trick, see the note under the docstring
+        return self.send(referee, ("", "song_added"), attrs, profile=profile)
 
     def playNext(self, room_jid, profile):
         """"Play next song in queue if exists, and put a timer
@@ -142,31 +206,33 @@
         # TODO: songs need to be erased once played or found invalids
         #      ==> unlink done the Q&D way with the same host trick (see above)
         radio_data = self.games[room_jid]
-        if len(radio_data['players']) == 0:
-            log.debug(_(u'No more participants in the radiocol: cleaning data'))
-            radio_data['queue'] = []
-            for filename in radio_data['to_delete']:
+        if len(radio_data["players"]) == 0:
+            log.debug(_(u"No more participants in the radiocol: cleaning data"))
+            radio_data["queue"] = []
+            for filename in radio_data["to_delete"]:
                 self.deleteFile(filename, radio_data)
-            radio_data['to_delete'] = {}
-        queue = radio_data['queue']
+            radio_data["to_delete"] = {}
+        queue = radio_data["queue"]
         if not queue:
             # nothing left to play, we need to wait for uploads
-            radio_data['playing'] = None
+            radio_data["playing"] = None
             return
         song = queue.pop(0)
-        filename, length = song['filename'], float(song['length'])
-        self.send(room_jid, ('', 'play'), {'filename': filename}, profile=profile)
-        radio_data['playing'] = song
-        radio_data['playing_time'] = time.time()
+        filename, length = song["filename"], float(song["length"])
+        self.send(room_jid, ("", "play"), {"filename": filename}, profile=profile)
+        radio_data["playing"] = song
+        radio_data["playing_time"] = time.time()
 
-        if not radio_data['upload'] and len(queue) < QUEUE_LIMIT:
+        if not radio_data["upload"] and len(queue) < QUEUE_LIMIT:
             # upload is blocked and we now have resources to get more, we reactivate it
-            self.send(room_jid, ('', 'upload_ok'), profile=profile)
-            radio_data['upload'] = True
+            self.send(room_jid, ("", "upload_ok"), profile=profile)
+            radio_data["upload"] = True
 
         reactor.callLater(length, self.playNext, room_jid, profile)
         # we wait more than the song length to delete the file, to manage poorly reactive networks/clients
-        reactor.callLater(length + 90, self.deleteFile, filename, radio_data)  # FIXME: same host trick (see above)
+        reactor.callLater(
+            length + 90, self.deleteFile, filename, radio_data
+        )  # FIXME: same host trick (see above)
 
     def deleteFile(self, filename, radio_data=None):
         """
@@ -177,61 +243,96 @@
         """
         if radio_data:
             try:
-                file_to_delete = radio_data['to_delete'][filename]
+                file_to_delete = radio_data["to_delete"][filename]
             except KeyError:
-                log.error(_(u"INTERNAL ERROR: can't find full path of the song to delete"))
+                log.error(
+                    _(u"INTERNAL ERROR: can't find full path of the song to delete")
+                )
                 return False
         else:
             file_to_delete = filename
         try:
             unlink(file_to_delete)
         except OSError:
-            log.error(_(u"INTERNAL ERROR: can't find %s on the file system" % file_to_delete))
+            log.error(
+                _(u"INTERNAL ERROR: can't find %s on the file system" % file_to_delete)
+            )
             return False
         return True
 
     def room_game_cmd(self, mess_elt, profile):
-        from_jid = jid.JID(mess_elt['from'])
+        from_jid = jid.JID(mess_elt["from"])
         room_jid = from_jid.userhostJID()
         nick = self.host.plugins["XEP-0045"].getRoomNick(room_jid, profile)
 
         radio_elt = mess_elt.firstChildElement()
         radio_data = self.games[room_jid]
-        if 'queue' in radio_data:
-            queue = radio_data['queue']
+        if "queue" in radio_data:
+            queue = radio_data["queue"]
 
         from_referee = self.isReferee(room_jid, from_jid.resource)
-        to_referee = self.isReferee(room_jid, jid.JID(mess_elt['to']).user)
+        to_referee = self.isReferee(room_jid, jid.JID(mess_elt["to"]).user)
         is_player = self.isPlayer(room_jid, nick)
         for elt in radio_elt.elements():
-            if not from_referee and not (to_referee and elt.name == 'song_added'):
+            if not from_referee and not (to_referee and elt.name == "song_added"):
                 continue  # sender must be referee, expect when a song is submitted
-            if not is_player and (elt.name not in ('started', 'players')):
+            if not is_player and (elt.name not in ("started", "players")):
                 continue  # user is in the room but not playing
 
-            if elt.name in ('started', 'players'):  # new game created and/or players list updated
+            if elt.name in (
+                "started",
+                "players",
+            ):  # new game created and/or players list updated
                 players = []
                 for player in elt.elements():
                     players.append(unicode(player))
-                signal = self.host.bridge.radiocolStarted if elt.name == 'started' else self.host.bridge.radiocolPlayers
-                signal(room_jid.userhost(), from_jid.full(), players, [QUEUE_TO_START, QUEUE_LIMIT], profile)
-            elif elt.name == 'preload':  # a song is in queue and must be preloaded
-                self.host.bridge.radiocolPreload(room_jid.userhost(), elt['timestamp'], elt['filename'], elt['title'], elt['artist'], elt['album'], elt['sender'], profile)
-            elif elt.name == 'play':
-                self.host.bridge.radiocolPlay(room_jid.userhost(), elt['filename'], profile)
-            elif elt.name == 'song_rejected':  # a song has been refused
-                self.host.bridge.radiocolSongRejected(room_jid.userhost(), elt['reason'], profile)
-            elif elt.name == 'no_upload':
+                signal = (
+                    self.host.bridge.radiocolStarted
+                    if elt.name == "started"
+                    else self.host.bridge.radiocolPlayers
+                )
+                signal(
+                    room_jid.userhost(),
+                    from_jid.full(),
+                    players,
+                    [QUEUE_TO_START, QUEUE_LIMIT],
+                    profile,
+                )
+            elif elt.name == "preload":  # a song is in queue and must be preloaded
+                self.host.bridge.radiocolPreload(
+                    room_jid.userhost(),
+                    elt["timestamp"],
+                    elt["filename"],
+                    elt["title"],
+                    elt["artist"],
+                    elt["album"],
+                    elt["sender"],
+                    profile,
+                )
+            elif elt.name == "play":
+                self.host.bridge.radiocolPlay(
+                    room_jid.userhost(), elt["filename"], profile
+                )
+            elif elt.name == "song_rejected":  # a song has been refused
+                self.host.bridge.radiocolSongRejected(
+                    room_jid.userhost(), elt["reason"], profile
+                )
+            elif elt.name == "no_upload":
                 self.host.bridge.radiocolNoUpload(room_jid.userhost(), profile)
-            elif elt.name == 'upload_ok':
+            elif elt.name == "upload_ok":
                 self.host.bridge.radiocolUploadOk(room_jid.userhost(), profile)
-            elif elt.name == 'song_added':  # a song has been added
+            elif elt.name == "song_added":  # a song has been added
                 # FIXME: we are KISS for the proof of concept: every song is added, to a limit of 3 in queue.
                 #       Need to manage some sort of rules to allow peoples to send songs
                 if len(queue) >= QUEUE_LIMIT:
                     # there are already too many songs in queue, we reject this one
                     # FIXME: add an error code
-                    self.send(from_jid, ('', 'song_rejected'), {'reason': "Too many songs in queue"}, profile=profile)
+                    self.send(
+                        from_jid,
+                        ("", "song_rejected"),
+                        {"reason": "Too many songs in queue"},
+                        profile=profile,
+                    )
                     return
 
                 # The song is accepted and added in queue
@@ -240,30 +341,30 @@
 
                 if len(queue) >= QUEUE_LIMIT:
                     # We are at the limit, we refuse new upload until next play
-                    self.send(room_jid, ('', 'no_upload'), profile=profile)
-                    radio_data['upload'] = False
+                    self.send(room_jid, ("", "no_upload"), profile=profile)
+                    radio_data["upload"] = False
 
                 self.send(room_jid, preload_elt, profile=profile)
-                if not radio_data['playing'] and len(queue) == QUEUE_TO_START:
+                if not radio_data["playing"] and len(queue) == QUEUE_TO_START:
                     # We have not started playing yet, and we have QUEUE_TO_START
                     # songs in queue. We can now start the party :)
                     self.playNext(room_jid, profile)
             else:
-                log.error(_(u'Unmanaged game element: %s') % elt.name)
+                log.error(_(u"Unmanaged game element: %s") % elt.name)
 
     def getSyncDataForPlayer(self, room_jid, nick):
         game_data = self.games[room_jid]
         elements = []
-        if game_data['playing']:
-            preload = copy.deepcopy(game_data['playing'])
-            current_time = game_data['playing_time'] + 1 if self.testing else time.time()
-            preload['filename'] += '#t=%.2f' % (current_time - game_data['playing_time'])
+        if game_data["playing"]:
+            preload = copy.deepcopy(game_data["playing"])
+            current_time = game_data["playing_time"] + 1 if self.testing else time.time()
+            preload["filename"] += "#t=%.2f" % (current_time - game_data["playing_time"])
             elements.append(preload)
-            play = domish.Element(('', 'play'))
-            play['filename'] = preload['filename']
+            play = domish.Element(("", "play"))
+            play["filename"] = preload["filename"]
             elements.append(play)
-        if len(game_data['queue']) > 0:
-            elements.extend(copy.deepcopy(game_data['queue']))
-            if len(game_data['queue']) == QUEUE_LIMIT:
-                elements.append(domish.Element(('', 'no_upload')))
+        if len(game_data["queue"]) > 0:
+            elements.extend(copy.deepcopy(game_data["queue"]))
+            if len(game_data["queue"]) == QUEUE_LIMIT:
+                elements.append(domish.Element(("", "no_upload")))
         return elements
--- a/sat/plugins/plugin_misc_register_account.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_misc_register_account.py	Wed Jun 27 20:14:46 2018 +0200
@@ -20,6 +20,7 @@
 
 from sat.core.i18n import _, D_
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from sat.core.constants import Const as C
 from twisted.words.protocols.jabber import jid
@@ -37,7 +38,7 @@
     C.PI_RECOMMENDATIONS: [],
     C.PI_MAIN: "RegisterAccount",
     C.PI_HANDLER: "no",
-    C.PI_DESCRIPTION: _(u"""Register XMPP account""")
+    C.PI_DESCRIPTION: _(u"""Register XMPP account"""),
 }
 
 
@@ -48,8 +49,12 @@
         log.info(_(u"Plugin Register Account initialization"))
         self.host = host
         self._sessions = Sessions()
-        host.registerCallback(self.registerNewAccountCB, with_data=True, force_id="registerNewAccount")
-        self.__register_account_id = host.registerCallback(self._registerConfirmation, with_data=True)
+        host.registerCallback(
+            self.registerNewAccountCB, with_data=True, force_id="registerNewAccount"
+        )
+        self.__register_account_id = host.registerCallback(
+            self._registerConfirmation, with_data=True
+        )
 
     def registerNewAccountCB(self, data, profile):
         """Called when the user click on the "New account" button."""
@@ -57,57 +62,92 @@
 
         # FIXME: following loop is overcomplicated, hard to read
         # FIXME: while used with parameters, hashed password is used and overwrite clear one
-        for param in (u'JabberID', u'Password', C.FORCE_PORT_PARAM, C.FORCE_SERVER_PARAM):
+        for param in (u"JabberID", u"Password", C.FORCE_PORT_PARAM, C.FORCE_SERVER_PARAM):
             try:
-                session_data[param] = data[SAT_FORM_PREFIX + u"Connection" + SAT_PARAM_SEPARATOR + param]
+                session_data[param] = data[
+                    SAT_FORM_PREFIX + u"Connection" + SAT_PARAM_SEPARATOR + param
+                ]
             except KeyError:
                 if param in (C.FORCE_PORT_PARAM, C.FORCE_SERVER_PARAM):
-                    session_data[param] = ''
+                    session_data[param] = ""
 
-        for param in (u'JabberID', u'Password'):
+        for param in (u"JabberID", u"Password"):
             if not session_data[param]:
                 form_ui = xml_tools.XMLUI(u"popup", title=D_(u"Missing values"))
-                form_ui.addText(D_(u"No user JID or password given: can't register new account."))
-                return  {u'xmlui': form_ui.toXml()}
+                form_ui.addText(
+                    D_(u"No user JID or password given: can't register new account.")
+                )
+                return {u"xmlui": form_ui.toXml()}
 
-        session_data['user'], host, resource = jid.parse(session_data['JabberID'])
-        session_data['server'] = session_data[C.FORCE_SERVER_PARAM] or host
+        session_data["user"], host, resource = jid.parse(session_data["JabberID"])
+        session_data["server"] = session_data[C.FORCE_SERVER_PARAM] or host
         session_id, dummy = self._sessions.newSession(session_data, profile=profile)
-        form_ui = xml_tools.XMLUI("form", title=D_("Register new account"), submit_id=self.__register_account_id, session_id=session_id)
-        form_ui.addText(D_(u"Do you want to register a new XMPP account {jid}?").format(
-            jid = session_data['JabberID']))
-        return  {'xmlui': form_ui.toXml()}
+        form_ui = xml_tools.XMLUI(
+            "form",
+            title=D_("Register new account"),
+            submit_id=self.__register_account_id,
+            session_id=session_id,
+        )
+        form_ui.addText(
+            D_(u"Do you want to register a new XMPP account {jid}?").format(
+                jid=session_data["JabberID"]
+            )
+        )
+        return {"xmlui": form_ui.toXml()}
 
     def _registerConfirmation(self, data, profile):
         """Save the related parameters and proceed the registration."""
-        session_data = self._sessions.profileGet(data['session_id'], profile)
+        session_data = self._sessions.profileGet(data["session_id"], profile)
 
-        self.host.memory.setParam("JabberID", session_data["JabberID"], "Connection", profile_key=profile)
-        self.host.memory.setParam("Password", session_data["Password"], "Connection", profile_key=profile)
-        self.host.memory.setParam(C.FORCE_SERVER_PARAM, session_data[C.FORCE_SERVER_PARAM], "Connection", profile_key=profile)
-        self.host.memory.setParam(C.FORCE_PORT_PARAM, session_data[C.FORCE_PORT_PARAM], "Connection", profile_key=profile)
+        self.host.memory.setParam(
+            "JabberID", session_data["JabberID"], "Connection", profile_key=profile
+        )
+        self.host.memory.setParam(
+            "Password", session_data["Password"], "Connection", profile_key=profile
+        )
+        self.host.memory.setParam(
+            C.FORCE_SERVER_PARAM,
+            session_data[C.FORCE_SERVER_PARAM],
+            "Connection",
+            profile_key=profile,
+        )
+        self.host.memory.setParam(
+            C.FORCE_PORT_PARAM,
+            session_data[C.FORCE_PORT_PARAM],
+            "Connection",
+            profile_key=profile,
+        )
 
-        d = self._registerNewAccount(jid.JID(session_data['JabberID']), session_data["Password"], None, session_data['server'])
-        del self._sessions[data['session_id']]
+        d = self._registerNewAccount(
+            jid.JID(session_data["JabberID"]),
+            session_data["Password"],
+            None,
+            session_data["server"],
+        )
+        del self._sessions[data["session_id"]]
         return d
 
     def _registerNewAccount(self, client, jid_, password, email, server):
-        # FIXME: port is not set here
+        #  FIXME: port is not set here
         def registeredCb(dummy):
             xmlui = xml_tools.XMLUI(u"popup", title=D_(u"Confirmation"))
             xmlui.addText(D_("Registration successful."))
-            return ({'xmlui': xmlui.toXml()})
+            return {"xmlui": xmlui.toXml()}
 
         def registeredEb(failure):
             xmlui = xml_tools.XMLUI("popup", title=D_("Failure"))
             xmlui.addText(D_("Registration failed: %s") % failure.getErrorMessage())
             try:
-                if failure.value.condition == 'conflict':
-                    xmlui.addText(D_("Username already exists, please choose an other one."))
+                if failure.value.condition == "conflict":
+                    xmlui.addText(
+                        D_("Username already exists, please choose an other one.")
+                    )
             except AttributeError:
                 pass
-            return ({'xmlui': xmlui.toXml()})
+            return {"xmlui": xmlui.toXml()}
 
-        registered_d = self.host.plugins['XEP-0077'].registerNewAccount(client, jid_, password, email=email, host=server, port=C.XMPP_C2S_PORT)
+        registered_d = self.host.plugins["XEP-0077"].registerNewAccount(
+            client, jid_, password, email=email, host=server, port=C.XMPP_C2S_PORT
+        )
         registered_d.addCallbacks(registeredCb, registeredEb)
         return registered_d
--- a/sat/plugins/plugin_misc_room_game.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_misc_room_game.py	Wed Jun 27 20:14:46 2018 +0200
@@ -20,6 +20,7 @@
 from sat.core.i18n import _
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from twisted.words.protocols.jabber import jid
 from twisted.words.xish import domish
@@ -28,6 +29,7 @@
 from wokkel import disco, iwokkel
 from zope.interface import implements
 import copy
+
 try:
     from twisted.words.protocols.xmlstream import XMPPHandler
 except ImportError:
@@ -44,12 +46,13 @@
     C.PI_DEPENDENCIES: ["XEP-0045", "XEP-0249"],
     C.PI_MAIN: "RoomGame",
     C.PI_HANDLER: "no",  # handler MUST be "no" (dynamic inheritance)
-    C.PI_DESCRIPTION: _("""Base class for MUC games""")
+    C.PI_DESCRIPTION: _("""Base class for MUC games"""),
 }
 
 
 # FIXME: this plugin is broken, need to be fixed
 
+
 class RoomGame(object):
     """This class is used to help launching a MUC game.
 
@@ -69,7 +72,7 @@
     # Values for ready_mode (how to turn a MUC user into a player)
     ASK, FORCE = xrange(0, 2)
 
-    MESSAGE = '/message'
+    MESSAGE = "/message"
     REQUEST = '%s/%s[@xmlns="%s"]'
 
     def __init__(self, host):
@@ -165,8 +168,13 @@
         # Important: do not add the referee to 'players' yet. For a
         # <players /> message to be emitted whenever a new player is joining,
         # it is necessary to not modify 'players' outside of _updatePlayers.
-        referee_jid = jid.JID(room_jid.userhost() + '/' + referee_nick)
-        self.games[room_jid] = {'referee': referee_jid, 'players': [], 'started': False, 'status': {}}
+        referee_jid = jid.JID(room_jid.userhost() + "/" + referee_nick)
+        self.games[room_jid] = {
+            "referee": referee_jid,
+            "players": [],
+            "started": False,
+            "status": {},
+        }
         self.games[room_jid].update(copy.deepcopy(self.game_init))
         self.invitations.setdefault(room_jid, [])
 
@@ -175,7 +183,7 @@
         @param started: if False, the game must be initialized to return True,
         otherwise it must be initialized and started with createGame.
         @return: True if a game is initialized/started in that room"""
-        return room_jid in self.games and (not started or self.games[room_jid]['started'])
+        return room_jid in self.games and (not started or self.games[room_jid]["started"])
 
     def _checkJoinAuth(self, room_jid, user_jid=None, nick="", verbose=False):
         """Checks if this profile is allowed to join the game.
@@ -209,7 +217,14 @@
                         break
 
         if not auth and (verbose or _DEBUG):
-            log.debug(_(u"%(user)s not allowed to join the game %(game)s in %(room)s") % {'user': user_jid.userhost() or nick, 'game': self.name, 'room': room_jid.userhost()})
+            log.debug(
+                _(u"%(user)s not allowed to join the game %(game)s in %(room)s")
+                % {
+                    "user": user_jid.userhost() or nick,
+                    "game": self.name,
+                    "room": room_jid.userhost(),
+                }
+            )
         return auth
 
     def _updatePlayers(self, room_jid, nicks, sync, profile):
@@ -224,20 +239,26 @@
         if nicks == []:
             return
         # this is better than set(nicks).difference(...) as it keeps the order
-        new_nicks = [nick for nick in nicks if nick not in self.games[room_jid]['players']]
+        new_nicks = [
+            nick for nick in nicks if nick not in self.games[room_jid]["players"]
+        ]
         if len(new_nicks) == 0:
             return
 
         def setStatus(status):
             for nick in new_nicks:
-                self.games[room_jid]['status'][nick] = status
+                self.games[room_jid]["status"][nick] = status
 
-        sync = sync and self._gameExists(room_jid, True) and len(self.games[room_jid]['players']) > 0
-        setStatus('desync' if sync else 'init')
-        self.games[room_jid]['players'].extend(new_nicks)
+        sync = (
+            sync
+            and self._gameExists(room_jid, True)
+            and len(self.games[room_jid]["players"]) > 0
+        )
+        setStatus("desync" if sync else "init")
+        self.games[room_jid]["players"].extend(new_nicks)
         self._synchronizeRoom(room_jid, [room_jid], profile)
         if sync:
-            setStatus('init')
+            setStatus("init")
 
     def _synchronizeRoom(self, room_jid, recipients, profile):
         """Communicate the list of players to the whole room or only to some users,
@@ -249,15 +270,17 @@
         @param profile (unicode): %(doc_profile)s
         """
         if self._gameExists(room_jid, started=True):
-            element = self._createStartElement(self.games[room_jid]['players'])
+            element = self._createStartElement(self.games[room_jid]["players"])
         else:
-            element = self._createStartElement(self.games[room_jid]['players'], name="players")
+            element = self._createStartElement(
+                self.games[room_jid]["players"], name="players"
+            )
         elements = [(element, None, None)]
 
         sync_args = []
         sync_data = self._getSyncData(room_jid)
         for nick in sync_data:
-            user_jid = jid.JID(room_jid.userhost() + '/' + nick)
+            user_jid = jid.JID(room_jid.userhost() + "/" + nick)
             if user_jid in recipients:
                 user_elements = copy.deepcopy(elements)
                 for child in sync_data[nick]:
@@ -265,7 +288,7 @@
                 recipients.remove(user_jid)
             else:
                 user_elements = [(child, None, None) for child in sync_data[nick]]
-            sync_args.append(([user_jid, user_elements], {'profile': profile}))
+            sync_args.append(([user_jid, user_elements], {"profile": profile}))
 
         for recipient in recipients:
             self._sendElements(recipient, elements, profile=profile)
@@ -283,8 +306,8 @@
         if not self._gameExists(room_jid):
             return {}
         data = {}
-        status = self.games[room_jid]['status']
-        nicks = [nick for nick in status if status[nick] == 'desync']
+        status = self.games[room_jid]["status"]
+        nicks = [nick for nick in status if status[nick] == "desync"]
         if force_nicks is None:
             force_nicks = []
         for nick in force_nicks:
@@ -316,13 +339,19 @@
         if not self._checkInviteAuth(room_jid, nick):
             return []
         # TODO: remove invitation waiting for too long, using the time data
-        self.invitations[room_jid].append((time(), [player.userhostJID() for player in other_players]))
+        self.invitations[room_jid].append(
+            (time(), [player.userhostJID() for player in other_players])
+        )
         nicks = []
         for player_jid in [player.userhostJID() for player in other_players]:
             # TODO: find a way to make it secure
-            other_nick = self.host.plugins["XEP-0045"].getRoomEntityNick(room_jid, player_jid, secure=self.testing)
+            other_nick = self.host.plugins["XEP-0045"].getRoomEntityNick(
+                room_jid, player_jid, secure=self.testing
+            )
             if other_nick is None:
-                self.host.plugins["XEP-0249"].invite(player_jid, room_jid, {"game": self.name}, profile)
+                self.host.plugins["XEP-0249"].invite(
+                    player_jid, room_jid, {"game": self.name}, profile
+                )
             else:
                 nicks.append(other_nick)
         return nicks
@@ -339,13 +368,18 @@
         if self.invite_mode == self.FROM_ALL or not self._gameExists(room_jid):
             auth = True
         elif self.invite_mode == self.FROM_NONE:
-            auth = not self._gameExists(room_jid, started=True) and self.isReferee(room_jid, nick)
+            auth = not self._gameExists(room_jid, started=True) and self.isReferee(
+                room_jid, nick
+            )
         elif self.invite_mode == self.FROM_REFEREE:
             auth = self.isReferee(room_jid, nick)
         elif self.invite_mode == self.FROM_PLAYERS:
             auth = self.isPlayer(room_jid, nick)
         if not auth and (verbose or _DEBUG):
-            log.debug(_(u"%(user)s not allowed to invite for the game %(game)s in %(room)s") % {'user': nick, 'game': self.name, 'room': room_jid.userhost()})
+            log.debug(
+                _(u"%(user)s not allowed to invite for the game %(game)s in %(room)s")
+                % {"user": nick, "game": self.name, "room": room_jid.userhost()}
+            )
         return auth
 
     def isReferee(self, room_jid, nick):
@@ -356,7 +390,9 @@
         """
         if not self._gameExists(room_jid):
             return False
-        return jid.JID(room_jid.userhost() + '/' + nick) == self.games[room_jid]['referee']
+        return (
+            jid.JID(room_jid.userhost() + "/" + nick) == self.games[room_jid]["referee"]
+        )
 
     def isPlayer(self, room_jid, nick):
         """Checks if the user with this nick is a player for the game in this room.
@@ -368,7 +404,7 @@
             return False
         # Important: the referee is not in the 'players' list right after
         # the game initialization, that's why we do also check with isReferee
-        return nick in self.games[room_jid]['players'] or self.isReferee(room_jid, nick)
+        return nick in self.games[room_jid]["players"] or self.isReferee(room_jid, nick)
 
     def _checkWaitAuth(self, room, other_players, verbose=False):
         """Check if we must wait for other players before starting the game.
@@ -388,10 +424,21 @@
             result = (False, [], other_players)
         else:
             # TODO: find a way to make it secure
-            (nicks, missing) = self.host.plugins["XEP-0045"].getRoomNicksOfUsers(room, other_players, secure=False)
+            (nicks, missing) = self.host.plugins["XEP-0045"].getRoomNicksOfUsers(
+                room, other_players, secure=False
+            )
             result = (len(nicks) == len(other_players), nicks, missing)
         if not result[0] and (verbose or _DEBUG):
-            log.debug(_(u"Still waiting for %(users)s before starting the game %(game)s in %(room)s") % {'users': result[2], 'game': self.name, 'room': room.occupantJID.userhost()})
+            log.debug(
+                _(
+                    u"Still waiting for %(users)s before starting the game %(game)s in %(room)s"
+                )
+                % {
+                    "users": result[2],
+                    "game": self.name,
+                    "room": room.occupantJID.userhost(),
+                }
+            )
         return result
 
     def getUniqueName(self, muc_service=None, profile_key=C.PROF_KEY_NONE):
@@ -406,7 +453,9 @@
         room = self.host.plugins["XEP-0045"].getUniqueName(client, muc_service)
         return jid.JID("sat_%s_%s" % (self.name.lower(), room.userhost()))
 
-    def _prepareRoom(self, other_players=None, room_jid_s='', profile_key=C.PROF_KEY_NONE):
+    def _prepareRoom(
+        self, other_players=None, room_jid_s="", profile_key=C.PROF_KEY_NONE
+    ):
         room_jid = jid.JID(room_jid_s) if room_jid_s else None
         other_players = [jid.JID(player).userhostJID() for player in other_players]
         return self.prepareRoom(other_players, room_jid, profile_key)
@@ -420,7 +469,7 @@
         """
         # FIXME: need to be refactored
         client = self.host.getClient(profile_key)
-        log.debug(_(u'Preparing room for %s game') % self.name)
+        log.debug(_(u"Preparing room for %s game") % self.name)
         profile = self.host.memory.getProfileName(profile_key)
         if not profile:
             log.error(_("Unknown profile"))
@@ -438,7 +487,9 @@
 
         user_jid = self.host.getJidNStream(profile)[0]
         d = self.host.plugins["XEP-0045"].join(room_jid, user_jid.user, {}, profile)
-        return d.addCallback(lambda dummy: self._createOrInvite(client, room_jid, other_players))
+        return d.addCallback(
+            lambda dummy: self._createOrInvite(client, room_jid, other_players)
+        )
 
     def userJoinedTrigger(self, room, user, profile):
         """This trigger is used to check if the new user can take part of a game, create the game if we were waiting for him or just update the players list.
@@ -451,15 +502,22 @@
         profile_nick = room.occupantJID.resource
         if not self.isReferee(room_jid, profile_nick):
             return True  # profile is not the referee
-        if not self._checkJoinAuth(room_jid, user.entity if user.entity else None, user.nick):
+        if not self._checkJoinAuth(
+            room_jid, user.entity if user.entity else None, user.nick
+        ):
             # user not allowed but let him know that we are playing :p
-            self._synchronizeRoom(room_jid, [jid.JID(room_jid.userhost() + '/' + user.nick)], profile)
+            self._synchronizeRoom(
+                room_jid, [jid.JID(room_jid.userhost() + "/" + user.nick)], profile
+            )
             return True
         if self.wait_mode == self.FOR_ALL:
             # considering the last batch of invitations
             batch = len(self.invitations[room_jid]) - 1
             if batch < 0:
-                log.error(u"Invitations from %s to play %s in %s have been lost!" % (profile_nick, self.name, room_jid.userhost()))
+                log.error(
+                    u"Invitations from %s to play %s in %s have been lost!"
+                    % (profile_nick, self.name, room_jid.userhost())
+                )
                 return True
             other_players = self.invitations[room_jid][batch][1]
             (auth, nicks, dummy) = self._checkWaitAuth(room, other_players)
@@ -485,10 +543,10 @@
             return True  # profile is not the referee
         if self.isPlayer(room_jid, user.nick):
             try:
-                self.games[room_jid]['players'].remove(user.nick)
+                self.games[room_jid]["players"].remove(user.nick)
             except ValueError:
                 pass
-            if len(self.games[room_jid]['players']) == 0:
+            if len(self.games[room_jid]["players"]) == 0:
                 return True
             if self.wait_mode == self.FOR_ALL:
                 # allow this user to join the game again
@@ -513,15 +571,24 @@
         """
         user_nick = self.host.plugins["XEP-0045"].getRoomNick(room_jid, profile)
         if not user_nick:
-            log.error(u'Internal error: profile %s has not joined the room %s' % (profile, room_jid.userhost()))
+            log.error(
+                u"Internal error: profile %s has not joined the room %s"
+                % (profile, room_jid.userhost())
+            )
             return False, False
         if self._gameExists(room_jid):
             is_referee = self.isReferee(room_jid, user_nick)
             if self._gameExists(room_jid, started=True):
-                log.info(_(u"%(game)s game already created in room %(room)s") % {'game': self.name, 'room': room_jid.userhost()})
+                log.info(
+                    _(u"%(game)s game already created in room %(room)s")
+                    % {"game": self.name, "room": room_jid.userhost()}
+                )
                 return False, is_referee
             elif not is_referee:
-                log.info(_(u"%(game)s game in room %(room)s can only be created by %(user)s") % {'game': self.name, 'room': room_jid.userhost(), 'user': user_nick})
+                log.info(
+                    _(u"%(game)s game in room %(room)s can only be created by %(user)s")
+                    % {"game": self.name, "room": room_jid.userhost(), "user": user_nick}
+                )
                 return False, False
         else:
             self._initGame(room_jid, user_nick)
@@ -539,7 +606,10 @@
         @param nicks (list[unicode]): list of players nicks in the room (referee included, in first position)
         @param profile_key (unicode): %(doc_profile_key)s
         """
-        log.debug(_(u"Creating %(game)s game in room %(room)s") % {'game': self.name, 'room': room_jid})
+        log.debug(
+            _(u"Creating %(game)s game in room %(room)s")
+            % {"game": self.name, "room": room_jid}
+        )
         profile = self.host.memory.getProfileName(profile_key)
         if not profile:
             log.error(_(u"profile %s is unknown") % profile_key)
@@ -551,14 +621,16 @@
             if sync:
                 self._updatePlayers(room_jid, nicks, True, profile)
             return
-        self.games[room_jid]['started'] = True
+        self.games[room_jid]["started"] = True
         self._updatePlayers(room_jid, nicks, False, profile)
         if self.player_init:
             # specific data to each player (score, private data)
-            self.games[room_jid].setdefault('players_data', {})
+            self.games[room_jid].setdefault("players_data", {})
             for nick in nicks:
                 # The dict must be COPIED otherwise it is shared between all users
-                self.games[room_jid]['players_data'][nick] = copy.deepcopy(self.player_init)
+                self.games[room_jid]["players_data"][nick] = copy.deepcopy(
+                    self.player_init
+                )
 
     def _playerReady(self, player_nick, referee_jid_s, profile_key=C.PROF_KEY_NONE):
         self.playerReady(player_nick, jid.JID(referee_jid_s), profile_key)
@@ -573,9 +645,9 @@
         if not profile:
             log.error(_(u"profile %s is unknown") % profile_key)
             return
-        log.debug(u'new player ready: %s' % profile)
+        log.debug(u"new player ready: %s" % profile)
         # TODO: we probably need to add the game and room names in the sent message
-        self.send(referee_jid, 'player_ready', {'player': player_nick}, profile=profile)
+        self.send(referee_jid, "player_ready", {"player": player_nick}, profile=profile)
 
     def newRound(self, room_jid, data, profile):
         """Launch a new round (reinit the user data)
@@ -586,18 +658,22 @@
                     - msg_elts: dict to map each user to his specific initialization message
         @param profile
         """
-        log.debug(_(u'new round for %s game') % self.name)
+        log.debug(_(u"new round for %s game") % self.name)
         game_data = self.games[room_jid]
-        players = game_data['players']
-        players_data = game_data['players_data']
-        game_data['stage'] = "init"
+        players = game_data["players"]
+        players_data = game_data["players_data"]
+        game_data["stage"] = "init"
 
         common_data, msg_elts = copy.deepcopy(data) if data is not None else (None, None)
 
         if isinstance(msg_elts, dict):
             for player in players:
                 to_jid = jid.JID(room_jid.userhost() + "/" + player)  # FIXME: gof:
-                elem = msg_elts[player] if isinstance(msg_elts[player], domish.Element) else None
+                elem = (
+                    msg_elts[player]
+                    if isinstance(msg_elts[player], domish.Element)
+                    else None
+                )
                 self.send(to_jid, elem, profile=profile)
         elif isinstance(msg_elts, domish.Element):
             self.send(room_jid, msg_elts, profile=profile)
@@ -612,7 +688,7 @@
         @return: the created element
         """
         type_ = "normal" if to_jid.resource else "groupchat"
-        elt = domish.Element((None, 'message'))
+        elt = domish.Element((None, "message"))
         elt["to"] = to_jid.full()
         elt["type"] = type_
         elt.addElement(self.ns_tag)
@@ -632,9 +708,9 @@
             return started_elt
         idx = 0
         for player in players:
-            player_elt = domish.Element((None, 'player'))
+            player_elt = domish.Element((None, "player"))
             player_elt.addContent(player)
-            player_elt['index'] = str(idx)
+            player_elt["index"] = str(idx)
             idx += 1
             started_elt.addChild(player_elt)
         return started_elt
@@ -687,7 +763,7 @@
         return RoomGameHandler(self)
 
 
-class RoomGameHandler (XMPPHandler):
+class RoomGameHandler(XMPPHandler):
     implements(iwokkel.IDisco)
 
     def __init__(self, plugin_parent):
@@ -695,10 +771,14 @@
         self.host = plugin_parent.host
 
     def connectionInitialized(self):
-        self.xmlstream.addObserver(self.plugin_parent.request, self.plugin_parent.room_game_cmd, profile=self.parent.profile)
+        self.xmlstream.addObserver(
+            self.plugin_parent.request,
+            self.plugin_parent.room_game_cmd,
+            profile=self.parent.profile,
+        )
 
-    def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
+    def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
         return [disco.DiscoFeature(self.plugin_parent.ns_tag[0])]
 
-    def getDiscoItems(self, requestor, target, nodeIdentifier=''):
+    def getDiscoItems(self, requestor, target, nodeIdentifier=""):
         return []
--- a/sat/plugins/plugin_misc_smtp.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_misc_smtp.py	Wed Jun 27 20:14:46 2018 +0200
@@ -20,6 +20,7 @@
 from sat.core.i18n import _
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from twisted.internet import defer
 from twisted.cred import portal, checkers, credentials
@@ -42,7 +43,9 @@
     C.PI_DEPENDENCIES: ["Maildir"],
     C.PI_MAIN: "SMTP_server",
     C.PI_HANDLER: "no",
-    C.PI_DESCRIPTION: _("""Create a SMTP server that you can use to send your "normal" type messages""")
+    C.PI_DESCRIPTION: _(
+        """Create a SMTP server that you can use to send your "normal" type messages"""
+    ),
 }
 
 
@@ -62,7 +65,7 @@
         log.info(_("Plugin SMTP Server initialization"))
         self.host = host
 
-        #parameters
+        # parameters
         host.memory.updateParams(self.params)
 
         port = int(self.host.memory.getParamA("SMTP Port", "Mail Server"))
@@ -88,11 +91,20 @@
         """handle end of message"""
         mail = Parser().parsestr("\n".join(self.message))
         try:
-            self.host._sendMessage(parseaddr(mail['to'].decode('utf-8', 'replace'))[1], mail.get_payload().decode('utf-8', 'replace'),  # TODO: manage other charsets
-                                  subject=mail['subject'].decode('utf-8', 'replace'), mess_type='normal', profile_key=self.profile)
+            self.host._sendMessage(
+                parseaddr(mail["to"].decode("utf-8", "replace"))[1],
+                mail.get_payload().decode(
+                    "utf-8", "replace"
+                ),  # TODO: manage other charsets
+                subject=mail["subject"].decode("utf-8", "replace"),
+                mess_type="normal",
+                profile_key=self.profile,
+            )
         except:
             exc_type, exc_value, exc_traceback = sys.exc_info()
-            log.error(_(u"Can't send message: %s") % exc_value)  # The email is invalid or incorreclty parsed
+            log.error(
+                _(u"Can't send message: %s") % exc_value
+            )  # The email is invalid or incorreclty parsed
             return defer.fail()
         self.message = None
         return defer.succeed(None)
@@ -151,8 +163,8 @@
         self.host = host
 
     def requestAvatar(self, avatarID, mind, *interfaces):
-        log.debug('requestAvatar')
-        profile = avatarID.decode('utf-8')
+        log.debug("requestAvatar")
+        profile = avatarID.decode("utf-8")
         if smtp.IMessageDelivery not in interfaces:
             raise NotImplementedError
         return smtp.IMessageDelivery, SatSmtpDelivery(self.host, profile), lambda: None
@@ -164,16 +176,19 @@
     Check if the profile exists, and if the password is OK
     Return the profile as avatarId
     """
+
     implements(checkers.ICredentialsChecker)
-    credentialInterfaces = (credentials.IUsernamePassword,
-                            credentials.IUsernameHashedPassword)
+    credentialInterfaces = (
+        credentials.IUsernamePassword,
+        credentials.IUsernameHashedPassword,
+    )
 
     def __init__(self, host):
         self.host = host
 
     def _cbPasswordMatch(self, matched, profile):
         if matched:
-            return profile.encode('utf-8')
+            return profile.encode("utf-8")
         else:
             return failure.Failure(cred_error.UnauthorizedLogin())
 
@@ -181,14 +196,15 @@
         profiles = self.host.memory.getProfilesList()
         if not credentials.username in profiles:
             return defer.fail(cred_error.UnauthorizedLogin())
-        d = self.host.memory.asyncGetParamA("Password", "Connection", profile_key=credentials.username)
+        d = self.host.memory.asyncGetParamA(
+            "Password", "Connection", profile_key=credentials.username
+        )
         d.addCallback(credentials.checkPassword)
         d.addCallback(self._cbPasswordMatch, credentials.username)
         return d
 
 
 class SmtpServerFactory(smtp.SMTPFactory):
-
     def __init__(self, host):
         self.protocol = smtp.ESMTP
         self.host = host
--- a/sat/plugins/plugin_misc_static_blog.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_misc_static_blog.py	Wed Jun 27 20:14:46 2018 +0200
@@ -18,6 +18,7 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 
 from sat.core.i18n import _, D_
@@ -35,10 +36,12 @@
     C.PI_TYPE: "MISC",
     C.PI_PROTOCOLS: [],
     C.PI_DEPENDENCIES: [],
-    C.PI_RECOMMENDATIONS: ['MISC-ACCOUNT'],  # TODO: remove when all blogs can be retrieved
+    C.PI_RECOMMENDATIONS: [
+        "MISC-ACCOUNT"
+    ],  # TODO: remove when all blogs can be retrieved
     C.PI_MAIN: "StaticBlog",
     C.PI_HANDLER: "no",
-    C.PI_DESCRIPTION: _("""Plugin for static blogs""")
+    C.PI_DESCRIPTION: _("""Plugin for static blogs"""),
 }
 
 
@@ -57,23 +60,23 @@
     </individual>
     </params>
     """.format(
-        category_name = C.STATIC_BLOG_KEY,
-        category_label = D_(C.STATIC_BLOG_KEY),
-        title_name = C.STATIC_BLOG_PARAM_TITLE,
-        title_label = D_('Page title'),
-        banner_name = C.STATIC_BLOG_PARAM_BANNER,
-        banner_label = D_('Banner URL'),
-        background_name = u"Background",
-        background_label = D_(u"Background image URL"),
-        keywords_name = C.STATIC_BLOG_PARAM_KEYWORDS,
-        keywords_label = D_('Keywords'),
-        description_name = C.STATIC_BLOG_PARAM_DESCRIPTION,
-        description_label = D_('Description'),
+        category_name=C.STATIC_BLOG_KEY,
+        category_label=D_(C.STATIC_BLOG_KEY),
+        title_name=C.STATIC_BLOG_PARAM_TITLE,
+        title_label=D_("Page title"),
+        banner_name=C.STATIC_BLOG_PARAM_BANNER,
+        banner_label=D_("Banner URL"),
+        background_name=u"Background",
+        background_label=D_(u"Background image URL"),
+        keywords_name=C.STATIC_BLOG_PARAM_KEYWORDS,
+        keywords_label=D_("Keywords"),
+        description_name=C.STATIC_BLOG_PARAM_DESCRIPTION,
+        description_label=D_("Description"),
     )
 
     def __init__(self, host):
         try:  # TODO: remove this attribute when all blogs can be retrieved
-            self.domain = host.plugins['MISC-ACCOUNT'].getNewAccountDomain()
+            self.domain = host.plugins["MISC-ACCOUNT"].getNewAccountDomain()
         except KeyError:
             self.domain = None
         host.memory.updateParams(self.params)
@@ -89,7 +92,7 @@
         # FIXME: "public_blog" key has been removed
         # TODO: replace this with a more generic widget call with URIs
         try:
-            user_jid = jid.JID(menu_data['jid'])
+            user_jid = jid.JID(menu_data["jid"])
         except KeyError:
             log.error(_("jid key is not present !"))
             return defer.fail(exceptions.DataError)
@@ -97,7 +100,9 @@
         # TODO: remove this check when all blogs can be retrieved
         if self.domain and user_jid.host != self.domain:
             info_ui = xml_tools.XMLUI("popup", title=D_("Not available"))
-            info_ui.addText(D_("Retrieving a blog from an external domain is not implemented yet."))
-            return {'xmlui': info_ui.toXml()}
+            info_ui.addText(
+                D_("Retrieving a blog from an external domain is not implemented yet.")
+            )
+            return {"xmlui": info_ui.toXml()}
 
         return {"public_blog": user_jid.userhost()}
--- a/sat/plugins/plugin_misc_tarot.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_misc_tarot.py	Wed Jun 27 20:14:46 2018 +0200
@@ -20,6 +20,7 @@
 from sat.core.i18n import _
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from twisted.words.xish import domish
 from twisted.words.protocols.jabber import jid
@@ -32,8 +33,8 @@
 import random
 
 
-NS_CG = 'http://www.goffi.org/protocol/card_game'
-CG_TAG = 'card_game'
+NS_CG = "http://www.goffi.org/protocol/card_game"
+CG_TAG = "card_game"
 
 PLUGIN_INFO = {
     C.PI_NAME: "Tarot cards plugin",
@@ -43,54 +44,117 @@
     C.PI_DEPENDENCIES: ["XEP-0045", "XEP-0249", "ROOM-GAME"],
     C.PI_MAIN: "Tarot",
     C.PI_HANDLER: "yes",
-    C.PI_DESCRIPTION: _("""Implementation of Tarot card game""")
+    C.PI_DESCRIPTION: _("""Implementation of Tarot card game"""),
 }
 
 
 class Tarot(object):
-
     def inheritFromRoomGame(self, host):
         global RoomGame
         RoomGame = host.plugins["ROOM-GAME"].__class__
-        self.__class__ = type(self.__class__.__name__, (self.__class__, RoomGame, object), {})
+        self.__class__ = type(
+            self.__class__.__name__, (self.__class__, RoomGame, object), {}
+        )
 
     def __init__(self, host):
         log.info(_("Plugin Tarot initialization"))
         self._sessions = memory.Sessions()
         self.inheritFromRoomGame(host)
-        RoomGame._init_(self, host, PLUGIN_INFO, (NS_CG, CG_TAG),
-                        game_init={'hand_size': 18, 'init_player': 0, 'current_player': None, 'contrat': None, 'stage': None},
-                        player_init={'score': 0})
-        self.contrats = [_('Passe'), _('Petite'), _('Garde'), _('Garde Sans'), _('Garde Contre')]
-        host.bridge.addMethod("tarotGameLaunch", ".plugin", in_sign='asss', out_sign='', method=self._prepareRoom, async=True)  # args: players, room_jid, profile
-        host.bridge.addMethod("tarotGameCreate", ".plugin", in_sign='sass', out_sign='', method=self._createGame)  # args: room_jid, players, profile
-        host.bridge.addMethod("tarotGameReady", ".plugin", in_sign='sss', out_sign='', method=self._playerReady)  # args: player, referee, profile
-        host.bridge.addMethod("tarotGamePlayCards", ".plugin", in_sign='ssa(ss)s', out_sign='', method=self.play_cards)  # args: player, referee, cards, profile
-        host.bridge.addSignal("tarotGamePlayers", ".plugin", signature='ssass')  # args: room_jid, referee, players, profile
-        host.bridge.addSignal("tarotGameStarted", ".plugin", signature='ssass')  # args: room_jid, referee, players, profile
-        host.bridge.addSignal("tarotGameNew", ".plugin", signature='sa(ss)s')  # args: room_jid, hand, profile
-        host.bridge.addSignal("tarotGameChooseContrat", ".plugin", signature='sss')  # args: room_jid, xml_data, profile
-        host.bridge.addSignal("tarotGameShowCards", ".plugin", signature='ssa(ss)a{ss}s')  # args: room_jid, type ["chien", "poignée",...], cards, data[dict], profile
-        host.bridge.addSignal("tarotGameCardsPlayed", ".plugin", signature='ssa(ss)s')  # args: room_jid, player, type ["chien", "poignée",...], cards, data[dict], profile
-        host.bridge.addSignal("tarotGameYourTurn", ".plugin", signature='ss')  # args: room_jid, profile
-        host.bridge.addSignal("tarotGameScore", ".plugin", signature='ssasass')  # args: room_jid, xml_data, winners (list of nicks), loosers (list of nicks), profile
-        host.bridge.addSignal("tarotGameInvalidCards", ".plugin", signature='ssa(ss)a(ss)s')  # args: room_jid, game phase, played_cards, invalid_cards, profile
+        RoomGame._init_(
+            self,
+            host,
+            PLUGIN_INFO,
+            (NS_CG, CG_TAG),
+            game_init={
+                "hand_size": 18,
+                "init_player": 0,
+                "current_player": None,
+                "contrat": None,
+                "stage": None,
+            },
+            player_init={"score": 0},
+        )
+        self.contrats = [
+            _("Passe"),
+            _("Petite"),
+            _("Garde"),
+            _("Garde Sans"),
+            _("Garde Contre"),
+        ]
+        host.bridge.addMethod(
+            "tarotGameLaunch",
+            ".plugin",
+            in_sign="asss",
+            out_sign="",
+            method=self._prepareRoom,
+            async=True,
+        )  # args: players, room_jid, profile
+        host.bridge.addMethod(
+            "tarotGameCreate",
+            ".plugin",
+            in_sign="sass",
+            out_sign="",
+            method=self._createGame,
+        )  # args: room_jid, players, profile
+        host.bridge.addMethod(
+            "tarotGameReady",
+            ".plugin",
+            in_sign="sss",
+            out_sign="",
+            method=self._playerReady,
+        )  # args: player, referee, profile
+        host.bridge.addMethod(
+            "tarotGamePlayCards",
+            ".plugin",
+            in_sign="ssa(ss)s",
+            out_sign="",
+            method=self.play_cards,
+        )  # args: player, referee, cards, profile
+        host.bridge.addSignal(
+            "tarotGamePlayers", ".plugin", signature="ssass"
+        )  # args: room_jid, referee, players, profile
+        host.bridge.addSignal(
+            "tarotGameStarted", ".plugin", signature="ssass"
+        )  # args: room_jid, referee, players, profile
+        host.bridge.addSignal(
+            "tarotGameNew", ".plugin", signature="sa(ss)s"
+        )  # args: room_jid, hand, profile
+        host.bridge.addSignal(
+            "tarotGameChooseContrat", ".plugin", signature="sss"
+        )  # args: room_jid, xml_data, profile
+        host.bridge.addSignal(
+            "tarotGameShowCards", ".plugin", signature="ssa(ss)a{ss}s"
+        )  # args: room_jid, type ["chien", "poignée",...], cards, data[dict], profile
+        host.bridge.addSignal(
+            "tarotGameCardsPlayed", ".plugin", signature="ssa(ss)s"
+        )  # args: room_jid, player, type ["chien", "poignée",...], cards, data[dict], profile
+        host.bridge.addSignal(
+            "tarotGameYourTurn", ".plugin", signature="ss"
+        )  # args: room_jid, profile
+        host.bridge.addSignal(
+            "tarotGameScore", ".plugin", signature="ssasass"
+        )  # args: room_jid, xml_data, winners (list of nicks), loosers (list of nicks), profile
+        host.bridge.addSignal(
+            "tarotGameInvalidCards", ".plugin", signature="ssa(ss)a(ss)s"
+        )  # args: room_jid, game phase, played_cards, invalid_cards, profile
         self.deck_ordered = []
-        for value in ['excuse'] + map(str, range(1, 22)):
+        for value in ["excuse"] + map(str, range(1, 22)):
             self.deck_ordered.append(TarotCard(("atout", value)))
         for suit in ["pique", "coeur", "carreau", "trefle"]:
             for value in map(str, range(1, 11)) + ["valet", "cavalier", "dame", "roi"]:
                 self.deck_ordered.append(TarotCard((suit, value)))
-        self.__choose_contrat_id = host.registerCallback(self._contratChoosed, with_data=True)
+        self.__choose_contrat_id = host.registerCallback(
+            self._contratChoosed, with_data=True
+        )
         self.__score_id = host.registerCallback(self._scoreShowed, with_data=True)
 
     def __card_list_to_xml(self, cards_list, elt_name):
         """Convert a card list to domish element"""
         cards_list_elt = domish.Element((None, elt_name))
         for card in cards_list:
-            card_elt = domish.Element((None, 'card'))
-            card_elt['suit'] = card.suit
-            card_elt['value'] = card.value
+            card_elt = domish.Element((None, "card"))
+            card_elt["suit"] = card.suit
+            card_elt["value"] = card.value
             cards_list_elt.addChild(card_elt)
         return cards_list_elt
 
@@ -98,14 +162,19 @@
         """Convert a domish element with cards to a list of tuples"""
         cards_list = []
         for card in cards_list_elt.elements():
-            cards_list.append((card['suit'], card['value']))
+            cards_list.append((card["suit"], card["value"]))
         return cards_list
 
     def __ask_contrat(self):
         """Create a element for asking contrat"""
-        contrat_elt = domish.Element((None, 'contrat'))
-        form = data_form.Form('form', title=_('contrat selection'))
-        field = data_form.Field('list-single', 'contrat', options=map(data_form.Option, self.contrats), required=True)
+        contrat_elt = domish.Element((None, "contrat"))
+        form = data_form.Form("form", title=_("contrat selection"))
+        field = data_form.Field(
+            "list-single",
+            "contrat",
+            options=map(data_form.Option, self.contrats),
+            required=True,
+        )
         form.addField(field)
         contrat_elt.addChild(form.toElement())
         return contrat_elt
@@ -116,18 +185,18 @@
         @param winners: list of unicode nicks of winners
         @param loosers: list of unicode nicks of loosers"""
 
-        score_elt = domish.Element((None, 'score'))
-        form = data_form.Form('form', title=_('scores'))
-        for line in scores.split('\n'):
-            field = data_form.Field('fixed', value=line)
+        score_elt = domish.Element((None, "score"))
+        form = data_form.Form("form", title=_("scores"))
+        for line in scores.split("\n"):
+            field = data_form.Field("fixed", value=line)
             form.addField(field)
         score_elt.addChild(form.toElement())
         for winner in winners:
-            winner_elt = domish.Element((None, 'winner'))
+            winner_elt = domish.Element((None, "winner"))
             winner_elt.addContent(winner)
             score_elt.addChild(winner_elt)
         for looser in loosers:
-            looser_elt = domish.Element((None, 'looser'))
+            looser_elt = domish.Element((None, "looser"))
             looser_elt.addContent(looser)
             score_elt.addChild(looser_elt)
         return score_elt
@@ -136,11 +205,11 @@
         """Create a element for invalid_cards error
         @param list_cards: list of Card
         @param game_phase: phase of the game ['ecart', 'play']"""
-        error_elt = domish.Element((None, 'error'))
-        played_elt = self.__card_list_to_xml(played_cards, 'played')
-        invalid_elt = self.__card_list_to_xml(invalid_cards, 'invalid')
-        error_elt['type'] = 'invalid_cards'
-        error_elt['phase'] = game_phase
+        error_elt = domish.Element((None, "error"))
+        played_elt = self.__card_list_to_xml(played_cards, "played")
+        invalid_elt = self.__card_list_to_xml(invalid_cards, "invalid")
+        error_elt["type"] = "invalid_cards"
+        error_elt["phase"] = game_phase
         error_elt.addChild(played_elt)
         error_elt.addChild(invalid_elt)
         return error_elt
@@ -150,23 +219,25 @@
         @param next_pl: if given, then next_player is forced to this one
         """
         if next_pl:
-            game_data['current_player'] = game_data['players'].index(next_pl)
+            game_data["current_player"] = game_data["players"].index(next_pl)
             return next_pl
         else:
-            pl_idx = game_data['current_player'] = (game_data['current_player'] + 1) % len(game_data['players'])
-            return game_data['players'][pl_idx]
+            pl_idx = game_data["current_player"] = (
+                game_data["current_player"] + 1
+            ) % len(game_data["players"])
+            return game_data["players"][pl_idx]
 
     def __winner(self, game_data):
         """give the nick of the player who win this trick"""
-        players_data = game_data['players_data']
-        first = game_data['first_player']
-        first_idx = game_data['players'].index(first)
+        players_data = game_data["players_data"]
+        first = game_data["first_player"]
+        first_idx = game_data["players"].index(first)
         suit_asked = None
         strongest = None
         winner = None
         for idx in [(first_idx + i) % 4 for i in range(4)]:
-            player = game_data['players'][idx]
-            card = players_data[player]['played']
+            player = game_data["players"][idx]
+            card = players_data[player]["played"]
             if card.value == "excuse":
                 continue
             if suit_asked is None:
@@ -183,22 +254,31 @@
         @param played: cards currently on the table
         @param winner: nick of the trick winner"""
         # TODO: manage the case where excuse is played on the last trick (and lost)
-        players_data = game_data['players_data']
+        players_data = game_data["players_data"]
         excuse = TarotCard(("atout", "excuse"))
 
         # we first check if the Excuse was already played
         # and if somebody is waiting for a card
-        for player in game_data['players']:
-            if players_data[player]['wait_for_low']:
+        for player in game_data["players"]:
+            if players_data[player]["wait_for_low"]:
                 # the excuse owner has to give a card to somebody
                 if winner == player:
                     # the excuse owner win the trick, we check if we have something to give
                     for card in played:
                         if card.points == 0.5:
-                            pl_waiting = players_data[player]['wait_for_low']
+                            pl_waiting = players_data[player]["wait_for_low"]
                             played.remove(card)
-                            players_data[pl_waiting]['levees'].append(card)
-                            log.debug(_(u'Player %(excuse_owner)s give %(card_waited)s to %(player_waiting)s for Excuse compensation') % {"excuse_owner": player, "card_waited": card, "player_waiting": pl_waiting})
+                            players_data[pl_waiting]["levees"].append(card)
+                            log.debug(
+                                _(
+                                    u"Player %(excuse_owner)s give %(card_waited)s to %(player_waiting)s for Excuse compensation"
+                                )
+                                % {
+                                    "excuse_owner": player,
+                                    "card_waited": card,
+                                    "player_waiting": pl_waiting,
+                                }
+                            )
                             return
                 return
 
@@ -207,8 +287,8 @@
             return
 
         excuse_player = None  # Who has played the Excuse ?
-        for player in game_data['players']:
-            if players_data[player]['played'] == excuse:
+        for player in game_data["players"]:
+            if players_data[player]["played"] == excuse:
                 excuse_player = player
                 break
 
@@ -218,7 +298,7 @@
         # first we remove the excuse from played cards
         played.remove(excuse)
         # then we give it back to the original owner
-        owner_levees = players_data[excuse_player]['levees']
+        owner_levees = players_data[excuse_player]["levees"]
         owner_levees.append(excuse)
         # finally we give a low card to the trick winner
         low_card = None
@@ -228,23 +308,43 @@
             if owner_levees[card_idx].points == 0.5:
                 low_card = owner_levees[card_idx]
                 del owner_levees[card_idx]
-                players_data[winner]['levees'].append(low_card)
-                log.debug(_(u'Player %(excuse_owner)s give %(card_waited)s to %(player_waiting)s for Excuse compensation') % {"excuse_owner": excuse_player, "card_waited": low_card, "player_waiting": winner})
+                players_data[winner]["levees"].append(low_card)
+                log.debug(
+                    _(
+                        u"Player %(excuse_owner)s give %(card_waited)s to %(player_waiting)s for Excuse compensation"
+                    )
+                    % {
+                        "excuse_owner": excuse_player,
+                        "card_waited": low_card,
+                        "player_waiting": winner,
+                    }
+                )
                 break
         if not low_card:  # The player has no low card yet
             # TODO: manage case when player never win a trick with low card
-            players_data[excuse_player]['wait_for_low'] = winner
-            log.debug(_(u"%(excuse_owner)s keep the Excuse but has not card to give, %(winner)s is waiting for one") % {'excuse_owner': excuse_player, 'winner': winner})
+            players_data[excuse_player]["wait_for_low"] = winner
+            log.debug(
+                _(
+                    u"%(excuse_owner)s keep the Excuse but has not card to give, %(winner)s is waiting for one"
+                )
+                % {"excuse_owner": excuse_player, "winner": winner}
+            )
 
     def __draw_game(self, game_data):
         """The game is draw, no score change
         @param game_data: data of the game
         @return: tuple with (string victory message, list of winners, list of loosers)"""
-        players_data = game_data['players_data']
-        scores_str = _('Draw game')
-        scores_str += '\n'
-        for player in game_data['players']:
-            scores_str += _(u"\n--\n%(player)s:\nscore for this game ==> %(score_game)i\ntotal score ==> %(total_score)i") % {'player': player, 'score_game': 0, 'total_score': players_data[player]['score']}
+        players_data = game_data["players_data"]
+        scores_str = _("Draw game")
+        scores_str += "\n"
+        for player in game_data["players"]:
+            scores_str += _(
+                u"\n--\n%(player)s:\nscore for this game ==> %(score_game)i\ntotal score ==> %(total_score)i"
+            ) % {
+                "player": player,
+                "score_game": 0,
+                "total_score": players_data[player]["score"],
+            }
         log.debug(scores_str)
 
         return (scores_str, [], [])
@@ -253,8 +353,8 @@
         """The game is finished, time to know who won :)
         @param game_data: data of the game
         @return: tuple with (string victory message, list of winners, list of loosers)"""
-        players_data = game_data['players_data']
-        levees = players_data[game_data['attaquant']]['levees']
+        players_data = game_data["players_data"]
+        levees = players_data[game_data["attaquant"]]["levees"]
         score = 0
         nb_bouts = 0
         bouts = []
@@ -266,15 +366,15 @@
 
         # We do a basic check on score calculation
         check_score = 0
-        defenseurs = game_data['players'][:]
-        defenseurs.remove(game_data['attaquant'])
+        defenseurs = game_data["players"][:]
+        defenseurs.remove(game_data["attaquant"])
         for defenseur in defenseurs:
-            for card in players_data[defenseur]['levees']:
+            for card in players_data[defenseur]["levees"]:
                 check_score += card.points
-        if game_data['contrat'] == "Garde Contre":
-            for card in game_data['chien']:
+        if game_data["contrat"] == "Garde Contre":
+            for card in game_data["chien"]:
                 check_score += card.points
-        assert (score + check_score == 91)
+        assert score + check_score == 91
 
         point_limit = None
         if nb_bouts == 3:
@@ -285,38 +385,61 @@
             point_limit = 51
         else:
             point_limit = 56
-        if game_data['contrat'] == 'Petite':
+        if game_data["contrat"] == "Petite":
             contrat_mult = 1
-        elif game_data['contrat'] == 'Garde':
+        elif game_data["contrat"] == "Garde":
             contrat_mult = 2
-        elif game_data['contrat'] == 'Garde Sans':
+        elif game_data["contrat"] == "Garde Sans":
             contrat_mult = 4
-        elif game_data['contrat'] == 'Garde Contre':
+        elif game_data["contrat"] == "Garde Contre":
             contrat_mult = 6
         else:
-            log.error(_('INTERNAL ERROR: contrat not managed (mispelled ?)'))
-            assert(False)
+            log.error(_("INTERNAL ERROR: contrat not managed (mispelled ?)"))
+            assert False
 
-        victory = (score >= point_limit)
+        victory = score >= point_limit
         margin = abs(score - point_limit)
         points_defenseur = (margin + 25) * contrat_mult * (-1 if victory else 1)
         winners = []
         loosers = []
         player_score = {}
-        for player in game_data['players']:
+        for player in game_data["players"]:
             # TODO: adjust this for 3 and 5 players variants
             # TODO: manage bonuses (petit au bout, poignée, chelem)
-            player_score[player] = points_defenseur if player != game_data['attaquant'] else points_defenseur * -3
-            players_data[player]['score'] += player_score[player]  # we add score of this game to the global score
+            player_score[player] = (
+                points_defenseur
+                if player != game_data["attaquant"]
+                else points_defenseur * -3
+            )
+            players_data[player]["score"] += player_score[
+                player
+            ]  # we add score of this game to the global score
             if player_score[player] > 0:
                 winners.append(player)
             else:
                 loosers.append(player)
 
-        scores_str = _(u'The attacker (%(attaquant)s) makes %(points)i and needs to make %(point_limit)i (%(nb_bouts)s oulder%(plural)s%(separator)s%(bouts)s): (s)he %(victory)s') % {'attaquant': game_data['attaquant'], 'points': score, 'point_limit': point_limit, 'nb_bouts': nb_bouts, 'plural': 's' if nb_bouts > 1 else '', 'separator': ': ' if nb_bouts != 0 else '', 'bouts': ','.join(map(str, bouts)), 'victory': 'wins' if victory else 'looses'}
-        scores_str += '\n'
-        for player in game_data['players']:
-            scores_str += _(u"\n--\n%(player)s:\nscore for this game ==> %(score_game)i\ntotal score ==> %(total_score)i") % {'player': player, 'score_game': player_score[player], 'total_score': players_data[player]['score']}
+        scores_str = _(
+            u"The attacker (%(attaquant)s) makes %(points)i and needs to make %(point_limit)i (%(nb_bouts)s oulder%(plural)s%(separator)s%(bouts)s): (s)he %(victory)s"
+        ) % {
+            "attaquant": game_data["attaquant"],
+            "points": score,
+            "point_limit": point_limit,
+            "nb_bouts": nb_bouts,
+            "plural": "s" if nb_bouts > 1 else "",
+            "separator": ": " if nb_bouts != 0 else "",
+            "bouts": ",".join(map(str, bouts)),
+            "victory": "wins" if victory else "looses",
+        }
+        scores_str += "\n"
+        for player in game_data["players"]:
+            scores_str += _(
+                u"\n--\n%(player)s:\nscore for this game ==> %(score_game)i\ntotal score ==> %(total_score)i"
+            ) % {
+                "player": player,
+                "score_game": player_score[player],
+                "total_score": players_data[player]["score"],
+            }
         log.debug(scores_str)
 
         return (scores_str, winners, loosers)
@@ -327,35 +450,37 @@
         @param cards: cards the player want to play
         @return forbidden_cards cards or empty list if cards are ok"""
         forbidden_cards = []
-        if game_data['stage'] == 'ecart':
+        if game_data["stage"] == "ecart":
             for card in cards:
                 if card.bout or card.value == "roi":
                     forbidden_cards.append(card)
                 # TODO: manage case where atouts (trumps) are in the dog
-        elif game_data['stage'] == 'play':
+        elif game_data["stage"] == "play":
             biggest_atout = None
             suit_asked = None
-            players = game_data['players']
-            players_data = game_data['players_data']
-            idx = players.index(game_data['first_player'])
-            current_idx = game_data['current_player']
+            players = game_data["players"]
+            players_data = game_data["players_data"]
+            idx = players.index(game_data["first_player"])
+            current_idx = game_data["current_player"]
             current_player = players[current_idx]
             if idx == current_idx:
                 # the player is the first to play, he can play what he wants
                 return forbidden_cards
-            while (idx != current_idx):
+            while idx != current_idx:
                 player = players[idx]
-                played_card = players_data[player]['played']
+                played_card = players_data[player]["played"]
                 if not suit_asked and played_card.value != "excuse":
                     suit_asked = played_card.suit
                 if played_card.suit == "atout" and played_card > biggest_atout:
                     biggest_atout = played_card
                 idx = (idx + 1) % len(players)
-            has_suit = False  # True if there is one card of the asked suit in the hand of the player
+            has_suit = (
+                False
+            )  # True if there is one card of the asked suit in the hand of the player
             has_atout = False
             biggest_hand_atout = None
 
-            for hand_card in game_data['hand'][current_player]:
+            for hand_card in game_data["hand"][current_player]:
                 if hand_card.suit == suit_asked:
                     has_suit = True
                 if hand_card.suit == "atout":
@@ -371,19 +496,28 @@
             if card.suit != suit_asked and card.suit != "atout" and has_atout:
                 forbidden_cards.append(card)
                 return forbidden_cards
-            if card.suit == "atout" and card < biggest_atout and biggest_hand_atout > biggest_atout and card.value != "excuse":
+            if (
+                card.suit == "atout"
+                and card < biggest_atout
+                and biggest_hand_atout > biggest_atout
+                and card.value != "excuse"
+            ):
                 forbidden_cards.append(card)
         else:
-            log.error(_('Internal error: unmanaged game stage'))
+            log.error(_("Internal error: unmanaged game stage"))
         return forbidden_cards
 
     def __start_play(self, room_jid, game_data, profile):
         """Start the game (tell to the first player after dealer to play"""
-        game_data['stage'] = "play"
-        next_player_idx = game_data['current_player'] = (game_data['init_player'] + 1) % len(game_data['players'])  # the player after the dealer start
-        game_data['first_player'] = next_player = game_data['players'][next_player_idx]
+        game_data["stage"] = "play"
+        next_player_idx = game_data["current_player"] = (
+            game_data["init_player"] + 1
+        ) % len(
+            game_data["players"]
+        )  # the player after the dealer start
+        game_data["first_player"] = next_player = game_data["players"][next_player_idx]
         to_jid = jid.JID(room_jid.userhost() + "/" + next_player)  # FIXME: gof:
-        self.send(to_jid, 'your_turn', profile=profile)
+        self.send(to_jid, "your_turn", profile=profile)
 
     def _contratChoosed(self, raw_data, profile):
         """Will be called when the contrat is selected
@@ -397,13 +531,22 @@
             # TODO: send error dialog
             return defer.succeed({})
 
-        room_jid = session_data['room_jid']
-        referee_jid = self.games[room_jid]['referee']
+        room_jid = session_data["room_jid"]
+        referee_jid = self.games[room_jid]["referee"]
         player = self.host.plugins["XEP-0045"].getRoomNick(room_jid, profile)
         data = xml_tools.XMLUIResult2DataFormResult(raw_data)
-        contrat = data['contrat']
-        log.debug(_(u'contrat [%(contrat)s] choosed by %(profile)s') % {'contrat': contrat, 'profile': profile})
-        d = self.send(referee_jid, ('', 'contrat_choosed'), {'player': player}, content=contrat, profile=profile)
+        contrat = data["contrat"]
+        log.debug(
+            _(u"contrat [%(contrat)s] choosed by %(profile)s")
+            % {"contrat": contrat, "profile": profile}
+        )
+        d = self.send(
+            referee_jid,
+            ("", "contrat_choosed"),
+            {"player": player},
+            content=contrat,
+            profile=profile,
+        )
         d.addCallback(lambda ignore: {})
         del self._sessions[raw_data["session_id"]]
         return d
@@ -420,7 +563,7 @@
             # TODO: send error dialog
             return defer.succeed({})
 
-        room_jid_s = session_data['room_jid'].userhost()
+        room_jid_s = session_data["room_jid"].userhost()
         # XXX: empty hand means to the frontend "reset the display"...
         self.host.bridge.tarotGameNew(room_jid_s, [], profile)
         del self._sessions[raw_data["session_id"]]
@@ -437,38 +580,44 @@
         if not profile:
             log.error(_(u"profile %s is unknown") % profile_key)
             return
-        log.debug(_(u'Cards played by %(profile)s: [%(cards)s]') % {'profile': profile, 'cards': cards})
-        elem = self.__card_list_to_xml(TarotCard.from_tuples(cards), 'cards_played')
-        self.send(jid.JID(referee), elem, {'player': player}, profile=profile)
+        log.debug(
+            _(u"Cards played by %(profile)s: [%(cards)s]")
+            % {"profile": profile, "cards": cards}
+        )
+        elem = self.__card_list_to_xml(TarotCard.from_tuples(cards), "cards_played")
+        self.send(jid.JID(referee), elem, {"player": player}, profile=profile)
 
     def newRound(self, room_jid, profile):
         game_data = self.games[room_jid]
-        players = game_data['players']
-        game_data['first_player'] = None  # first player for the current trick
-        game_data['contrat'] = None
-        common_data = {'contrat': None,
-                       'levees': [],  # cards won
-                       'played': None,  # card on the table
-                       'wait_for_low': None  # Used when a player wait for a low card because of excuse
-                       }
+        players = game_data["players"]
+        game_data["first_player"] = None  # first player for the current trick
+        game_data["contrat"] = None
+        common_data = {
+            "contrat": None,
+            "levees": [],  # cards won
+            "played": None,  # card on the table
+            "wait_for_low": None,  # Used when a player wait for a low card because of excuse
+        }
 
-        hand = game_data['hand'] = {}
-        hand_size = game_data['hand_size']
-        chien = game_data['chien'] = []
+        hand = game_data["hand"] = {}
+        hand_size = game_data["hand_size"]
+        chien = game_data["chien"] = []
         deck = self.deck_ordered[:]
         random.shuffle(deck)
         for i in range(4):
             hand[players[i]] = deck[0:hand_size]
             del deck[0:hand_size]
         chien.extend(deck)
-        del(deck[:])
+        del (deck[:])
         msg_elts = {}
         for player in players:
-            msg_elts[player] = self.__card_list_to_xml(hand[player], 'hand')
+            msg_elts[player] = self.__card_list_to_xml(hand[player], "hand")
 
         RoomGame.newRound(self, room_jid, (common_data, msg_elts), profile)
 
-        pl_idx = game_data['current_player'] = (game_data['init_player'] + 1) % len(players)  # the player after the dealer start
+        pl_idx = game_data["current_player"] = (game_data["init_player"] + 1) % len(
+            players
+        )  # the player after the dealer start
         player = players[pl_idx]
         to_jid = jid.JID(room_jid.userhost() + "/" + player)  # FIXME: gof:
         self.send(to_jid, self.__ask_contrat(), profile=profile)
@@ -478,52 +627,70 @@
         @param mess_elt: instance of twisted.words.xish.domish.Element
         """
         client = self.host.getClient(profile)
-        from_jid = jid.JID(mess_elt['from'])
+        from_jid = jid.JID(mess_elt["from"])
         room_jid = jid.JID(from_jid.userhost())
         nick = self.host.plugins["XEP-0045"].getRoomNick(client, room_jid)
 
         game_elt = mess_elt.firstChildElement()
         game_data = self.games[room_jid]
         is_player = self.isPlayer(room_jid, nick)
-        if 'players_data' in game_data:
-            players_data = game_data['players_data']
+        if "players_data" in game_data:
+            players_data = game_data["players_data"]
 
         for elt in game_elt.elements():
-            if not is_player and (elt.name not in ('started', 'players')):
+            if not is_player and (elt.name not in ("started", "players")):
                 continue  # user is in the room but not playing
 
-            if elt.name in ('started', 'players'):  # new game created and/or players list updated
+            if elt.name in (
+                "started",
+                "players",
+            ):  # new game created and/or players list updated
                 players = []
                 for player in elt.elements():
                     players.append(unicode(player))
-                signal = self.host.bridge.tarotGameStarted if elt.name == 'started' else self.host.bridge.tarotGamePlayers
+                signal = (
+                    self.host.bridge.tarotGameStarted
+                    if elt.name == "started"
+                    else self.host.bridge.tarotGamePlayers
+                )
                 signal(room_jid.userhost(), from_jid.full(), players, profile)
 
-            elif elt.name == 'player_ready':  # ready to play
-                player = elt['player']
-                status = self.games[room_jid]['status']
-                nb_players = len(self.games[room_jid]['players'])
-                status[player] = 'ready'
-                log.debug(_(u'Player %(player)s is ready to start [status: %(status)s]') % {'player': player, 'status': status})
-                if status.values().count('ready') == nb_players:  # everybody is ready, we can start the game
+            elif elt.name == "player_ready":  # ready to play
+                player = elt["player"]
+                status = self.games[room_jid]["status"]
+                nb_players = len(self.games[room_jid]["players"])
+                status[player] = "ready"
+                log.debug(
+                    _(u"Player %(player)s is ready to start [status: %(status)s]")
+                    % {"player": player, "status": status}
+                )
+                if (
+                    status.values().count("ready") == nb_players
+                ):  # everybody is ready, we can start the game
                     self.newRound(room_jid, profile)
 
-            elif elt.name == 'hand':  # a new hand has been received
-                self.host.bridge.tarotGameNew(room_jid.userhost(), self.__xml_to_list(elt), profile)
+            elif elt.name == "hand":  # a new hand has been received
+                self.host.bridge.tarotGameNew(
+                    room_jid.userhost(), self.__xml_to_list(elt), profile
+                )
 
-            elif elt.name == 'contrat':  # it's time to choose contrat
+            elif elt.name == "contrat":  # it's time to choose contrat
                 form = data_form.Form.fromElement(elt.firstChildElement())
                 session_id, session_data = self._sessions.newSession(profile=profile)
                 session_data["room_jid"] = room_jid
-                xml_data = xml_tools.dataForm2XMLUI(form, self.__choose_contrat_id, session_id).toXml()
-                self.host.bridge.tarotGameChooseContrat(room_jid.userhost(), xml_data, profile)
+                xml_data = xml_tools.dataForm2XMLUI(
+                    form, self.__choose_contrat_id, session_id
+                ).toXml()
+                self.host.bridge.tarotGameChooseContrat(
+                    room_jid.userhost(), xml_data, profile
+                )
 
-            elif elt.name == 'contrat_choosed':
+            elif elt.name == "contrat_choosed":
                 # TODO: check we receive the contrat from the right person
                 # TODO: use proper XEP-0004 way for answering form
-                player = elt['player']
-                players_data[player]['contrat'] = unicode(elt)
-                contrats = [players_data[p]['contrat'] for p in game_data['players']]
+                player = elt["player"]
+                players_data[player]["contrat"] = unicode(elt)
+                contrats = [players_data[p]["contrat"] for p in game_data["players"]]
                 if contrats.count(None):
                     # not everybody has choosed his contrat, it's next one turn
                     player = self.__next_player(game_data)
@@ -531,8 +698,8 @@
                     self.send(to_jid, self.__ask_contrat(), profile=profile)
                 else:
                     best_contrat = [None, "Passe"]
-                    for player in game_data['players']:
-                        contrat = players_data[player]['contrat']
+                    for player in game_data["players"]:
+                        contrat = players_data[player]["contrat"]
                         idx_best = self.contrats.index(best_contrat[1])
                         idx_pl = self.contrats.index(contrat)
                         if idx_pl > idx_best:
@@ -541,132 +708,194 @@
                     if best_contrat[1] == "Passe":
                         log.debug(_("Everybody is passing, round ended"))
                         to_jid = jid.JID(room_jid.userhost())
-                        self.send(to_jid, self.__give_scores(*self.__draw_game(game_data)), profile=profile)
-                        game_data['init_player'] = (game_data['init_player'] + 1) % len(game_data['players'])  # we change the dealer
-                        for player in game_data['players']:
-                            game_data['status'][player] = "init"
+                        self.send(
+                            to_jid,
+                            self.__give_scores(*self.__draw_game(game_data)),
+                            profile=profile,
+                        )
+                        game_data["init_player"] = (game_data["init_player"] + 1) % len(
+                            game_data["players"]
+                        )  # we change the dealer
+                        for player in game_data["players"]:
+                            game_data["status"][player] = "init"
                         return
-                    log.debug(_(u"%(player)s win the bid with %(contrat)s") % {'player': best_contrat[0], 'contrat': best_contrat[1]})
-                    game_data['contrat'] = best_contrat[1]
+                    log.debug(
+                        _(u"%(player)s win the bid with %(contrat)s")
+                        % {"player": best_contrat[0], "contrat": best_contrat[1]}
+                    )
+                    game_data["contrat"] = best_contrat[1]
 
-                    if game_data['contrat'] == "Garde Sans" or game_data['contrat'] == "Garde Contre":
+                    if (
+                        game_data["contrat"] == "Garde Sans"
+                        or game_data["contrat"] == "Garde Contre"
+                    ):
                         self.__start_play(room_jid, game_data, profile)
-                        game_data['attaquant'] = best_contrat[0]
+                        game_data["attaquant"] = best_contrat[0]
                     else:
                         # Time to show the chien to everybody
                         to_jid = jid.JID(room_jid.userhost())  # FIXME: gof:
-                        elem = self.__card_list_to_xml(game_data['chien'], 'chien')
-                        self.send(to_jid, elem, {'attaquant': best_contrat[0]}, profile=profile)
+                        elem = self.__card_list_to_xml(game_data["chien"], "chien")
+                        self.send(
+                            to_jid, elem, {"attaquant": best_contrat[0]}, profile=profile
+                        )
                         # the attacker (attaquant) get the chien
-                        game_data['hand'][best_contrat[0]].extend(game_data['chien'])
-                        del game_data['chien'][:]
+                        game_data["hand"][best_contrat[0]].extend(game_data["chien"])
+                        del game_data["chien"][:]
 
-                    if game_data['contrat'] == "Garde Sans":
+                    if game_data["contrat"] == "Garde Sans":
                         # The chien go into attaquant's (attacker) levees
-                        players_data[best_contrat[0]]['levees'].extend(game_data['chien'])
-                        del game_data['chien'][:]
+                        players_data[best_contrat[0]]["levees"].extend(game_data["chien"])
+                        del game_data["chien"][:]
 
-            elif elt.name == 'chien':  # we have received the chien
+            elif elt.name == "chien":  # we have received the chien
                 log.debug(_("tarot: chien received"))
-                data = {"attaquant": elt['attaquant']}
-                game_data['stage'] = "ecart"
-                game_data['attaquant'] = elt['attaquant']
-                self.host.bridge.tarotGameShowCards(room_jid.userhost(), "chien", self.__xml_to_list(elt), data, profile)
+                data = {"attaquant": elt["attaquant"]}
+                game_data["stage"] = "ecart"
+                game_data["attaquant"] = elt["attaquant"]
+                self.host.bridge.tarotGameShowCards(
+                    room_jid.userhost(), "chien", self.__xml_to_list(elt), data, profile
+                )
 
-            elif elt.name == 'cards_played':
-                if game_data['stage'] == "ecart":
+            elif elt.name == "cards_played":
+                if game_data["stage"] == "ecart":
                     # TODO: show atouts (trumps) if player put some in écart
-                    assert (game_data['attaquant'] == elt['player'])  # TODO: throw an xml error here
+                    assert (
+                        game_data["attaquant"] == elt["player"]
+                    )  # TODO: throw an xml error here
                     list_cards = TarotCard.from_tuples(self.__xml_to_list(elt))
                     # we now check validity of card
                     invalid_cards = self.__invalid_cards(game_data, list_cards)
                     if invalid_cards:
-                        elem = self.__invalid_cards_elt(list_cards, invalid_cards, game_data['stage'])
-                        self.send(jid.JID(room_jid.userhost() + '/' + elt['player']), elem, profile=profile)
+                        elem = self.__invalid_cards_elt(
+                            list_cards, invalid_cards, game_data["stage"]
+                        )
+                        self.send(
+                            jid.JID(room_jid.userhost() + "/" + elt["player"]),
+                            elem,
+                            profile=profile,
+                        )
                         return
 
                     # FIXME: gof: manage Garde Sans & Garde Contre cases
-                    players_data[elt['player']]['levees'].extend(list_cards)  # we add the chien to attaquant's levées
+                    players_data[elt["player"]]["levees"].extend(
+                        list_cards
+                    )  # we add the chien to attaquant's levées
                     for card in list_cards:
-                        game_data['hand'][elt['player']].remove(card)
+                        game_data["hand"][elt["player"]].remove(card)
 
                     self.__start_play(room_jid, game_data, profile)
 
-                elif game_data['stage'] == "play":
-                    current_player = game_data['players'][game_data['current_player']]
+                elif game_data["stage"] == "play":
+                    current_player = game_data["players"][game_data["current_player"]]
                     cards = TarotCard.from_tuples(self.__xml_to_list(elt))
 
-                    if mess_elt['type'] == 'groupchat':
-                        self.host.bridge.tarotGameCardsPlayed(room_jid.userhost(), elt['player'], self.__xml_to_list(elt), profile)
+                    if mess_elt["type"] == "groupchat":
+                        self.host.bridge.tarotGameCardsPlayed(
+                            room_jid.userhost(),
+                            elt["player"],
+                            self.__xml_to_list(elt),
+                            profile,
+                        )
                     else:
                         # we first check validity of card
                         invalid_cards = self.__invalid_cards(game_data, cards)
                         if invalid_cards:
-                            elem = self.__invalid_cards_elt(cards, invalid_cards, game_data['stage'])
-                            self.send(jid.JID(room_jid.userhost() + '/' + current_player), elem, profile=profile)
+                            elem = self.__invalid_cards_elt(
+                                cards, invalid_cards, game_data["stage"]
+                            )
+                            self.send(
+                                jid.JID(room_jid.userhost() + "/" + current_player),
+                                elem,
+                                profile=profile,
+                            )
                             return
                         # the card played is ok, we forward it to everybody
                         # first we remove it from the hand and put in on the table
-                        game_data['hand'][current_player].remove(cards[0])
-                        players_data[current_player]['played'] = cards[0]
+                        game_data["hand"][current_player].remove(cards[0])
+                        players_data[current_player]["played"] = cards[0]
 
                         # then we forward the message
                         self.send(room_jid, elt, profile=profile)
 
                         # Did everybody played ?
-                        played = [players_data[player]['played'] for player in game_data['players']]
+                        played = [
+                            players_data[player]["played"]
+                            for player in game_data["players"]
+                        ]
                         if all(played):
                             # everybody has played
                             winner = self.__winner(game_data)
-                            log.debug(_(u'The winner of this trick is %s') % winner)
+                            log.debug(_(u"The winner of this trick is %s") % winner)
                             # the winner win the trick
                             self.__excuse_hack(game_data, played, winner)
-                            players_data[elt['player']]['levees'].extend(played)
+                            players_data[elt["player"]]["levees"].extend(played)
                             # nothing left on the table
-                            for player in game_data['players']:
-                                players_data[player]['played'] = None
-                            if len(game_data['hand'][current_player]) == 0:
+                            for player in game_data["players"]:
+                                players_data[player]["played"] = None
+                            if len(game_data["hand"][current_player]) == 0:
                                 # no card left: the game is finished
-                                elem = self.__give_scores(*self.__calculate_scores(game_data))
+                                elem = self.__give_scores(
+                                    *self.__calculate_scores(game_data)
+                                )
                                 self.send(room_jid, elem, profile=profile)
-                                game_data['init_player'] = (game_data['init_player'] + 1) % len(game_data['players'])  # we change the dealer
-                                for player in game_data['players']:
-                                    game_data['status'][player] = "init"
+                                game_data["init_player"] = (
+                                    game_data["init_player"] + 1
+                                ) % len(
+                                    game_data["players"]
+                                )  # we change the dealer
+                                for player in game_data["players"]:
+                                    game_data["status"][player] = "init"
                                 return
                             # next player is the winner
-                            next_player = game_data['first_player'] = self.__next_player(game_data, winner)
+                            next_player = game_data["first_player"] = self.__next_player(
+                                game_data, winner
+                            )
                         else:
                             next_player = self.__next_player(game_data)
 
                         # finally, we tell to the next player to play
                         to_jid = jid.JID(room_jid.userhost() + "/" + next_player)
-                        self.send(to_jid, 'your_turn', profile=profile)
+                        self.send(to_jid, "your_turn", profile=profile)
 
-            elif elt.name == 'your_turn':
+            elif elt.name == "your_turn":
                 self.host.bridge.tarotGameYourTurn(room_jid.userhost(), profile)
 
-            elif elt.name == 'score':
-                form_elt = elt.elements(name='x', uri='jabber:x:data').next()
+            elif elt.name == "score":
+                form_elt = elt.elements(name="x", uri="jabber:x:data").next()
                 winners = []
                 loosers = []
-                for winner in elt.elements(name='winner', uri=NS_CG):
+                for winner in elt.elements(name="winner", uri=NS_CG):
                     winners.append(unicode(winner))
-                for looser in elt.elements(name='looser', uri=NS_CG):
+                for looser in elt.elements(name="looser", uri=NS_CG):
                     loosers.append(unicode(looser))
                 form = data_form.Form.fromElement(form_elt)
                 session_id, session_data = self._sessions.newSession(profile=profile)
                 session_data["room_jid"] = room_jid
-                xml_data = xml_tools.dataForm2XMLUI(form, self.__score_id, session_id).toXml()
-                self.host.bridge.tarotGameScore(room_jid.userhost(), xml_data, winners, loosers, profile)
-            elif elt.name == 'error':
-                if elt['type'] == 'invalid_cards':
-                    played_cards = self.__xml_to_list(elt.elements(name='played', uri=NS_CG).next())
-                    invalid_cards = self.__xml_to_list(elt.elements(name='invalid', uri=NS_CG).next())
-                    self.host.bridge.tarotGameInvalidCards(room_jid.userhost(), elt['phase'], played_cards, invalid_cards, profile)
+                xml_data = xml_tools.dataForm2XMLUI(
+                    form, self.__score_id, session_id
+                ).toXml()
+                self.host.bridge.tarotGameScore(
+                    room_jid.userhost(), xml_data, winners, loosers, profile
+                )
+            elif elt.name == "error":
+                if elt["type"] == "invalid_cards":
+                    played_cards = self.__xml_to_list(
+                        elt.elements(name="played", uri=NS_CG).next()
+                    )
+                    invalid_cards = self.__xml_to_list(
+                        elt.elements(name="invalid", uri=NS_CG).next()
+                    )
+                    self.host.bridge.tarotGameInvalidCards(
+                        room_jid.userhost(),
+                        elt["phase"],
+                        played_cards,
+                        invalid_cards,
+                        profile,
+                    )
                 else:
-                    log.error(_(u'Unmanaged error type: %s') % elt['type'])
+                    log.error(_(u"Unmanaged error type: %s") % elt["type"])
             else:
-                log.error(_(u'Unmanaged card game element: %s') % elt.name)
+                log.error(_(u"Unmanaged card game element: %s") % elt.name)
 
     def getSyncDataForPlayer(self, room_jid, nick):
         return []
--- a/sat/plugins/plugin_misc_text_commands.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_misc_text_commands.py	Wed Jun 27 20:14:46 2018 +0200
@@ -23,6 +23,7 @@
 from twisted.words.protocols.jabber import jid
 from twisted.internet import defer
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from twisted.python import failure
 from collections import OrderedDict
@@ -35,26 +36,29 @@
     C.PI_DEPENDENCIES: [],
     C.PI_MAIN: "TextCommands",
     C.PI_HANDLER: "no",
-    C.PI_DESCRIPTION: _("""IRC like text commands""")
+    C.PI_DESCRIPTION: _("""IRC like text commands"""),
 }
 
 
 class InvalidCommandSyntax(Exception):
     """Throwed while parsing @command in docstring if syntax is invalid"""
+
     pass
 
 
 CMD_KEY = "@command"
-CMD_TYPES = ('group', 'one2one', 'all')
+CMD_TYPES = ("group", "one2one", "all")
 FEEDBACK_INFO_TYPE = "TEXT_CMD"
 
 
 class TextCommands(object):
-    #FIXME: doc strings for commands have to be translatable
+    # FIXME: doc strings for commands have to be translatable
     #       plugins need a dynamic translation system (translation
     #       should be downloadable independently)
 
-    HELP_SUGGESTION = _("Type '/help' to get a list of the available commands. If you didn't want to use a command, please start your message with '//' to escape the slash.")
+    HELP_SUGGESTION = _(
+        "Type '/help' to get a list of the available commands. If you didn't want to use a command, please start your message with '//' to escape the slash."
+    )
 
     def __init__(self, host):
         log.info(_("Text commands initialization"))
@@ -78,9 +82,7 @@
             - "args" (default: ""): the arguments available, using syntax specified in documentation.
             - "doc_arg_[name]": the doc of [name] argument
         """
-        data = OrderedDict([('doc_short_help', ""),
-                            ('type', 'all'),
-                            ('args', '')])
+        data = OrderedDict([("doc_short_help", ""), ("type", "all"), ("args", "")])
         docstring = cmd.__doc__
         if docstring is None:
             log.warning(u"Not docstring found for command {}".format(cmd_name))
@@ -90,18 +92,22 @@
         data["doc_short_help"] = doc_data[0]
 
         try:
-            cmd_indent = 0 # >0 when @command is found are we are parsing it
+            cmd_indent = 0  # >0 when @command is found are we are parsing it
 
             for line in doc_data:
                 stripped = line.strip()
-                if cmd_indent and line[cmd_indent:cmd_indent+5] == "    -":
+                if cmd_indent and line[cmd_indent : cmd_indent + 5] == "    -":
                     colon_idx = line.find(":")
                     if colon_idx == -1:
-                        raise InvalidCommandSyntax("No colon found in argument description")
-                    arg_name = line[cmd_indent+6:colon_idx].strip()
+                        raise InvalidCommandSyntax(
+                            "No colon found in argument description"
+                        )
+                    arg_name = line[cmd_indent + 6 : colon_idx].strip()
                     if not arg_name:
-                        raise InvalidCommandSyntax("No name found in argument description")
-                    arg_help = line[colon_idx+1:].strip()
+                        raise InvalidCommandSyntax(
+                            "No name found in argument description"
+                        )
+                    arg_help = line[colon_idx + 1 :].strip()
                     data["doc_arg_{}".format(arg_name)] = arg_help
                 elif cmd_indent:
                     # we are parsing command and indent level is not good, it's finished
@@ -113,31 +119,36 @@
                     colon_idx = stripped.find(":")
                     if colon_idx == -1:
                         raise InvalidCommandSyntax("missing colon")
-                    type_data = stripped[len(CMD_KEY):colon_idx].strip()
+                    type_data = stripped[len(CMD_KEY) : colon_idx].strip()
                     if len(type_data) == 0:
-                        type_data="(all)"
-                    elif len(type_data) <= 2 or type_data[0] != '(' or type_data[-1] != ')':
+                        type_data = "(all)"
+                    elif (
+                        len(type_data) <= 2 or type_data[0] != "(" or type_data[-1] != ")"
+                    ):
                         raise InvalidCommandSyntax("Bad type data syntax")
                     type_ = type_data[1:-1]
                     if type_ not in CMD_TYPES:
                         raise InvalidCommandSyntax("Unknown type {}".format(type_))
                     data["type"] = type_
 
-                    #args
-                    data["args"] = stripped[colon_idx+1:].strip()
+                    # args
+                    data["args"] = stripped[colon_idx + 1 :].strip()
         except InvalidCommandSyntax as e:
-            log.warning(u"Invalid command syntax for command {command}: {message}".format(command=cmd_name, message=e.message))
+            log.warning(
+                u"Invalid command syntax for command {command}: {message}".format(
+                    command=cmd_name, message=e.message
+                )
+            )
 
         return data
 
-
     def registerTextCommands(self, instance):
         """ Add a text command
 
         @param instance: instance of a class containing text commands
         """
         for attr in dir(instance):
-            if attr.startswith('cmd_'):
+            if attr.startswith("cmd_"):
                 cmd = getattr(instance, attr)
                 if not callable(cmd):
                     log.warning(_(u"Skipping not callable [%s] attribute") % attr)
@@ -146,13 +157,19 @@
                 if not cmd_name:
                     log.warning(_("Skipping cmd_ method"))
                 if cmd_name in self._commands:
-                    suff=2
+                    suff = 2
                     while (cmd_name + str(suff)) in self._commands:
-                        suff+=1
+                        suff += 1
                     new_name = cmd_name + str(suff)
-                    log.warning(_(u"Conflict for command [{old_name}], renaming it to [{new_name}]").format(old_name=cmd_name, new_name=new_name))
+                    log.warning(
+                        _(
+                            u"Conflict for command [{old_name}], renaming it to [{new_name}]"
+                        ).format(old_name=cmd_name, new_name=new_name)
+                    )
                     cmd_name = new_name
-                self._commands[cmd_name] = cmd_data = OrderedDict({'callback':cmd}) # We use an Ordered dict to keep documenation order
+                self._commands[cmd_name] = cmd_data = OrderedDict(
+                    {"callback": cmd}
+                )  # We use an Ordered dict to keep documenation order
                 cmd_data.update(self._parseDocString(cmd, cmd_name))
                 log.info(_("Registered text command [%s]") % cmd_name)
 
@@ -168,7 +185,9 @@
         self._whois.append((priority, callback))
         self._whois.sort(key=lambda item: item[0], reverse=True)
 
-    def sendMessageTrigger(self, client, mess_data, pre_xml_treatments, post_xml_treatments):
+    def sendMessageTrigger(
+        self, client, mess_data, pre_xml_treatments, post_xml_treatments
+    ):
         """Install SendMessage command hook """
         pre_xml_treatments.addCallback(self._sendMessageCmdHook, client)
         return True
@@ -185,8 +204,8 @@
         @param profile: %(doc_profile)s
         """
         try:
-            msg = mess_data["message"]['']
-            msg_lang = ''
+            msg = mess_data["message"][""]
+            msg_lang = ""
         except KeyError:
             try:
                 # we have not default message, we try to take the first found
@@ -196,18 +215,18 @@
                 return mess_data
 
         try:
-            if msg[:2] == '//':
+            if msg[:2] == "//":
                 # we have a double '/', it's the escape sequence
                 mess_data["message"][msg_lang] = msg[1:]
                 return mess_data
-            if msg[0] != '/':
+            if msg[0] != "/":
                 return mess_data
         except IndexError:
             return mess_data
 
         # we have a command
         d = None
-        command = msg[1:].partition(' ')[0].lower()
+        command = msg[1:].partition(" ")[0].lower()
         if command.isalpha():
             # looks like an actual command, we try to call the corresponding method
             def retHandling(ret):
@@ -229,19 +248,33 @@
                 self.feedBack(client, u"Command failed {}".format(msg), mess_data)
                 return False
 
-            mess_data["unparsed"] = msg[1 + len(command):]  # part not yet parsed of the message
+            mess_data["unparsed"] = msg[
+                1 + len(command) :
+            ]  # part not yet parsed of the message
             try:
                 cmd_data = self._commands[command]
             except KeyError:
-                self.feedBack(client, _("Unknown command /%s. ") % command + self.HELP_SUGGESTION, mess_data)
+                self.feedBack(
+                    client,
+                    _("Unknown command /%s. ") % command + self.HELP_SUGGESTION,
+                    mess_data,
+                )
                 log.debug("text command help message")
                 raise failure.Failure(exceptions.CancelError())
             else:
                 if not self._contextValid(mess_data, cmd_data):
                     # The command is not launched in the right context, we throw a message with help instructions
-                    context_txt = _("group discussions") if cmd_data["type"] == "group" else _("one to one discussions")
-                    feedback = _("/{command} command only applies in {context}.").format(command=command, context=context_txt)
-                    self.feedBack(client, u"{} {}".format(feedback, self.HELP_SUGGESTION), mess_data)
+                    context_txt = (
+                        _("group discussions")
+                        if cmd_data["type"] == "group"
+                        else _("one to one discussions")
+                    )
+                    feedback = _("/{command} command only applies in {context}.").format(
+                        command=command, context=context_txt
+                    )
+                    self.feedBack(
+                        client, u"{} {}".format(feedback, self.HELP_SUGGESTION), mess_data
+                    )
                     log.debug("text command invalid message")
                     raise failure.Failure(exceptions.CancelError())
                 else:
@@ -249,7 +282,9 @@
                     d.addErrback(genericErrback)
                     d.addCallback(retHandling)
 
-        return d or mess_data  # if a command is detected, we should have a deferred, else we send the message normally
+        return (
+            d or mess_data
+        )  # if a command is detected, we should have a deferred, else we send the message normally
 
     def _contextValid(self, mess_data, cmd_data):
         """Tell if a command can be used in the given context
@@ -258,8 +293,9 @@
         @param cmd_data(dict): command data as returned by self._parseDocString
         @return (bool): True if command can be used in this context
         """
-        if ((cmd_data['type'] == "group" and mess_data["type"] != "groupchat") or
-            (cmd_data['type'] == 'one2one' and mess_data["type"] == "groupchat")):
+        if (cmd_data["type"] == "group" and mess_data["type"] != "groupchat") or (
+            cmd_data["type"] == "one2one" and mess_data["type"] == "groupchat"
+        ):
             return False
         return True
 
@@ -270,16 +306,16 @@
                     or a shortcut (e.g.: sat or sat@ for sat on current service)
         @param service_jid: jid of the current service (e.g.: chat.jabberfr.org)
         """
-        nb_arobas = arg.count('@')
+        nb_arobas = arg.count("@")
         if nb_arobas == 1:
-            if arg[-1] != '@':
+            if arg[-1] != "@":
                 return jid.JID(arg)
             return jid.JID(arg + service_jid)
         return jid.JID(u"%s@%s" % (arg, service_jid))
 
     def feedBack(self, client, message, mess_data, info_type=FEEDBACK_INFO_TYPE):
         """Give a message back to the user"""
-        if mess_data["type"] == 'groupchat':
+        if mess_data["type"] == "groupchat":
             to_ = mess_data["to"].userhostJID()
         else:
             to_ = client.jid
@@ -288,7 +324,7 @@
         mess_data["from"] = mess_data["to"]
         mess_data["to"] = to_
         mess_data["type"] = C.MESS_TYPE_INFO
-        mess_data["message"] = {'': message}
+        mess_data["message"] = {"": message}
         mess_data["extra"]["info_type"] = info_type
         client.messageSendToBridge(mess_data)
 
@@ -303,7 +339,7 @@
 
         entity = mess_data["unparsed"].strip()
 
-        if mess_data['type'] == "groupchat":
+        if mess_data["type"] == "groupchat":
             room = mess_data["to"].userhostJID()
             try:
                 if self.host.plugins["XEP-0045"].isNickInRoom(client, room, entity):
@@ -325,11 +361,13 @@
         if not target_jid.resource:
             target_jid.resource = self.host.memory.getMainResource(client, target_jid)
 
-        whois_msg = [_(u"whois for %(jid)s") % {'jid': target_jid}]
+        whois_msg = [_(u"whois for %(jid)s") % {"jid": target_jid}]
 
         d = defer.succeed(None)
         for ignore, callback in self._whois:
-            d.addCallback(lambda ignore: callback(client, whois_msg, mess_data, target_jid))
+            d.addCallback(
+                lambda ignore: callback(client, whois_msg, mess_data, target_jid)
+            )
 
         def feedBack(ignore):
             self.feedBack(client, u"\n".join(whois_msg), mess_data)
@@ -348,7 +386,9 @@
         for doc_name, doc_help in cmd_data.iteritems():
             if doc_name.startswith("doc_arg_"):
                 arg_name = doc_name[8:]
-                strings.append(u"- {name}: {doc_help}".format(name=arg_name, doc_help=_(doc_help)))
+                strings.append(
+                    u"- {name}: {doc_help}".format(name=arg_name, doc_help=_(doc_help))
+                )
 
         return strings
 
@@ -376,7 +416,9 @@
         if cmd_name and cmd_name[0] == "/":
             cmd_name = cmd_name[1:]
         if cmd_name and cmd_name not in self._commands:
-            self.feedBack(client, _(u"Invalid command name [{}]\n".format(cmd_name)), mess_data)
+            self.feedBack(
+                client, _(u"Invalid command name [{}]\n".format(cmd_name)), mess_data
+            )
             cmd_name = ""
         if not cmd_name:
             # we show the global help
@@ -387,22 +429,27 @@
                 cmd_data = self._commands[command]
                 if not self._contextValid(mess_data, cmd_data):
                     continue
-                spaces = (longuest - len(command)) * ' '
-                help_cmds.append("    /{command}: {spaces} {short_help}".format(
-                    command=command,
-                    spaces=spaces,
-                    short_help=cmd_data["doc_short_help"]
-                    ))
+                spaces = (longuest - len(command)) * " "
+                help_cmds.append(
+                    "    /{command}: {spaces} {short_help}".format(
+                        command=command,
+                        spaces=spaces,
+                        short_help=cmd_data["doc_short_help"],
+                    )
+                )
 
-            help_mess = _(u"Text commands available:\n%s") % (u'\n'.join(help_cmds), )
+            help_mess = _(u"Text commands available:\n%s") % (u"\n".join(help_cmds),)
         else:
             # we show detailled help for a command
             cmd_data = self._commands[cmd_name]
             syntax = cmd_data["args"]
             help_mess = _(u"/{name}: {short_help}\n{syntax}{args_help}").format(
                 name=cmd_name,
-                short_help=cmd_data['doc_short_help'],
-                syntax=_(" "*4+"syntax: {}\n").format(syntax) if syntax else "",
-                args_help=u'\n'.join([u" "*8+"{}".format(line) for line in self._getArgsHelp(cmd_data)]))
+                short_help=cmd_data["doc_short_help"],
+                syntax=_(" " * 4 + "syntax: {}\n").format(syntax) if syntax else "",
+                args_help=u"\n".join(
+                    [u" " * 8 + "{}".format(line) for line in self._getArgsHelp(cmd_data)]
+                ),
+            )
 
         self.feedBack(client, help_mess, mess_data)
--- a/sat/plugins/plugin_misc_text_syntaxes.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_misc_text_syntaxes.py	Wed Jun 27 20:14:46 2018 +0200
@@ -20,16 +20,20 @@
 from sat.core.i18n import _, D_
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 
 from twisted.internet import defer
 from twisted.internet.threads import deferToThread
 from sat.core import exceptions
+
 try:
     from lxml import html
     from lxml.html import clean
 except ImportError:
-    raise exceptions.MissingModule(u"Missing module lxml, please download/install it from http://lxml.de/")
+    raise exceptions.MissingModule(
+        u"Missing module lxml, please download/install it from http://lxml.de/"
+    )
 from cgi import escape
 import re
 
@@ -41,15 +45,69 @@
 
 # TODO: check/adapt following list
 # list initialy based on feedparser list (http://pythonhosted.org/feedparser/html-sanitization.html)
-STYLES_WHITELIST = ("azimuth", "background-color", "border-bottom-color", "border-collapse", "border-color", "border-left-color", "border-right-color", "border-top-color", "clear", "color", "cursor", "direction", "display", "elevation", "float", "font", "font-family", "font-size", "font-style", "font-variant", "font-weight", "height", "letter-spacing", "line-height", "overflow", "pause", "pause-after", "pause-before", "pitch", "pitch-range", "richness", "speak", "speak-header", "speak-numeral", "speak-punctuation", "speech-rate", "stress", "text-align", "text-decoration", "text-indent", "unicode-bidi", "vertical-align", "voice-family", "volume", "white-space", "width")
+STYLES_WHITELIST = (
+    "azimuth",
+    "background-color",
+    "border-bottom-color",
+    "border-collapse",
+    "border-color",
+    "border-left-color",
+    "border-right-color",
+    "border-top-color",
+    "clear",
+    "color",
+    "cursor",
+    "direction",
+    "display",
+    "elevation",
+    "float",
+    "font",
+    "font-family",
+    "font-size",
+    "font-style",
+    "font-variant",
+    "font-weight",
+    "height",
+    "letter-spacing",
+    "line-height",
+    "overflow",
+    "pause",
+    "pause-after",
+    "pause-before",
+    "pitch",
+    "pitch-range",
+    "richness",
+    "speak",
+    "speak-header",
+    "speak-numeral",
+    "speak-punctuation",
+    "speech-rate",
+    "stress",
+    "text-align",
+    "text-decoration",
+    "text-indent",
+    "unicode-bidi",
+    "vertical-align",
+    "voice-family",
+    "volume",
+    "white-space",
+    "width",
+)
 
-SAFE_ATTRS = html.defs.safe_attrs.union(('style', 'poster', 'controls'))
-STYLES_VALUES_REGEX = r'^(' + '|'.join(['([a-z-]+)', # alphabetical names
-                                       '(#[0-9a-f]+)', # hex value
-                                       '(\d+(.\d+)? *(|%|em|ex|px|in|cm|mm|pt|pc))', # values with units (or not)
-                                       'rgb\( *((\d+(.\d+)?), *){2}(\d+(.\d+)?) *\)', # rgb function
-                                       'rgba\( *((\d+(.\d+)?), *){3}(\d+(.\d+)?) *\)', # rgba function
-                                      ]) + ') *(!important)?$' # we accept "!important" at the end
+SAFE_ATTRS = html.defs.safe_attrs.union(("style", "poster", "controls"))
+STYLES_VALUES_REGEX = (
+    r"^("
+    + "|".join(
+        [
+            "([a-z-]+)",  # alphabetical names
+            "(#[0-9a-f]+)",  # hex value
+            "(\d+(.\d+)? *(|%|em|ex|px|in|cm|mm|pt|pc))",  # values with units (or not)
+            "rgb\( *((\d+(.\d+)?), *){2}(\d+(.\d+)?) *\)",  # rgb function
+            "rgba\( *((\d+(.\d+)?), *){3}(\d+(.\d+)?) *\)",  # rgba function
+        ]
+    )
+    + ") *(!important)?$"
+)  # we accept "!important" at the end
 STYLES_ACCEPTED_VALUE = re.compile(STYLES_VALUES_REGEX)
 
 PLUGIN_INFO = {
@@ -60,7 +118,9 @@
     C.PI_DEPENDENCIES: [],
     C.PI_MAIN: "TextSyntaxes",
     C.PI_HANDLER: "no",
-    C.PI_DESCRIPTION: _("""Management of various text syntaxes (XHTML-IM, Markdown, etc)""")
+    C.PI_DESCRIPTION: _(
+        """Management of various text syntaxes (XHTML-IM, Markdown, etc)"""
+    ),
 }
 
 
@@ -91,36 +151,60 @@
     """
 
     params_data = {
-        'category_name': CATEGORY,
-        'category_label': _(CATEGORY),
-        'name': NAME,
-        'label': _(NAME),
-        'syntaxes': syntaxes,
-        }
+        "category_name": CATEGORY,
+        "category_label": _(CATEGORY),
+        "name": NAME,
+        "label": _(NAME),
+        "syntaxes": syntaxes,
+    }
 
     def __init__(self, host):
         log.info(_("Text syntaxes plugin initialization"))
         self.host = host
-        self.addSyntax(self.SYNTAX_XHTML, lambda xhtml: defer.succeed(xhtml), lambda xhtml: defer.succeed(xhtml),
-                       TextSyntaxes.OPT_NO_THREAD)
+        self.addSyntax(
+            self.SYNTAX_XHTML,
+            lambda xhtml: defer.succeed(xhtml),
+            lambda xhtml: defer.succeed(xhtml),
+            TextSyntaxes.OPT_NO_THREAD,
+        )
         # TODO: text => XHTML should add <a/> to url like in frontends
         #       it's probably best to move sat_frontends.tools.strings to sat.tools.common or similar
-        self.addSyntax(self.SYNTAX_TEXT, lambda text: escape(text), lambda xhtml: self._removeMarkups(xhtml), [TextSyntaxes.OPT_HIDDEN])
+        self.addSyntax(
+            self.SYNTAX_TEXT,
+            lambda text: escape(text),
+            lambda xhtml: self._removeMarkups(xhtml),
+            [TextSyntaxes.OPT_HIDDEN],
+        )
         try:
             import markdown, html2text
 
-            def _html2text(html, baseurl=''):
+            def _html2text(html, baseurl=""):
                 h = html2text.HTML2Text(baseurl=baseurl)
                 h.body_width = 0  # do not truncate the lines, it breaks the long URLs
                 return h.handle(html)
-            self.addSyntax(self.SYNTAX_MARKDOWN, markdown.markdown, _html2text, [TextSyntaxes.OPT_DEFAULT])
+
+            self.addSyntax(
+                self.SYNTAX_MARKDOWN,
+                markdown.markdown,
+                _html2text,
+                [TextSyntaxes.OPT_DEFAULT],
+            )
         except ImportError:
             log.warning(u"markdown or html2text not found, can't use Markdown syntax")
-            log.info(u"You can download/install them from https://pythonhosted.org/Markdown/ and https://github.com/Alir3z4/html2text/")
-        host.bridge.addMethod("syntaxConvert", ".plugin", in_sign='sssbs', out_sign='s',
-                              async=True, method=self.convert)
-        host.bridge.addMethod("syntaxGet", ".plugin", in_sign='s', out_sign='s',
-                              method=self.getSyntax)
+            log.info(
+                u"You can download/install them from https://pythonhosted.org/Markdown/ and https://github.com/Alir3z4/html2text/"
+            )
+        host.bridge.addMethod(
+            "syntaxConvert",
+            ".plugin",
+            in_sign="sssbs",
+            out_sign="s",
+            async=True,
+            method=self.convert,
+        )
+        host.bridge.addMethod(
+            "syntaxGet", ".plugin", in_sign="s", out_sign="s", method=self.getSyntax
+        )
 
     def _updateParamOptions(self):
         data_synt = TextSyntaxes.syntaxes
@@ -136,10 +220,10 @@
         options = []
 
         for syntax in syntaxes:
-            selected = 'selected="true"' if syntax == default_synt else ''
+            selected = 'selected="true"' if syntax == default_synt else ""
             options.append(u'<option value="%s" %s/>' % (syntax, selected))
 
-        TextSyntaxes.params_data["options"] = u'\n'.join(options)
+        TextSyntaxes.params_data["options"] = u"\n".join(options)
         self.host.memory.updateParams(TextSyntaxes.params % TextSyntaxes.params_data)
 
     def getCurrentSyntax(self, profile):
@@ -148,16 +232,19 @@
         @param profile: %(doc_profile)s
         @return: profile selected syntax
         """
-        return self.host.memory.getParamA(NAME, CATEGORY , profile_key=profile)
+        return self.host.memory.getParamA(NAME, CATEGORY, profile_key=profile)
 
     def _logError(self, failure, action=u"converting syntax"):
-        log.error(u"Error while {action}: {failure}".format(action=action, failure=failure))
+        log.error(
+            u"Error while {action}: {failure}".format(action=action, failure=failure)
+        )
         return failure
 
     def cleanXHTML(self, xhtml):
         """ Clean XHTML text by removing potentially dangerous/malicious parts
         @param xhtml: raw xhtml text to clean (or lxml's HtmlElement)
         """
+
         def blocking_cleaning(xhtml):
             """ Clean XHTML and style attributes """
 
@@ -168,7 +255,7 @@
                 cleaned_styles = []
                 for style in styles:
                     try:
-                        key, value = style.split(':')
+                        key, value = style.split(":")
                     except ValueError:
                         continue
                     key = key.lower().strip()
@@ -180,7 +267,9 @@
                     if value == "none":
                         continue
                     cleaned_styles.append((key, value))
-                return "; ".join(["%s: %s" % (key_, value_) for key_, value_ in cleaned_styles])
+                return "; ".join(
+                    ["%s: %s" % (key_, value_) for key_, value_ in cleaned_styles]
+                )
 
             if isinstance(xhtml, basestring):
                 xhtml_elt = html.fromstring(xhtml)
@@ -189,19 +278,21 @@
             else:
                 log.error("Only strings and HtmlElements can be cleaned")
                 raise exceptions.DataError
-            cleaner = clean.Cleaner(style=False,
-                                    add_nofollow=False,
-                                    safe_attrs=SAFE_ATTRS)
+            cleaner = clean.Cleaner(
+                style=False, add_nofollow=False, safe_attrs=SAFE_ATTRS
+            )
             xhtml_elt = cleaner.clean_html(xhtml_elt)
             for elt in xhtml_elt.xpath("//*[@style]"):
-                elt.set("style", clean_style(elt.get('style')))
-            return html.tostring(xhtml_elt, encoding=unicode, method='xml')
+                elt.set("style", clean_style(elt.get("style")))
+            return html.tostring(xhtml_elt, encoding=unicode, method="xml")
 
         d = deferToThread(blocking_cleaning, xhtml)
         d.addErrback(self._logError, action=u"cleaning syntax")
         return d
 
-    def convert(self, text, syntax_from, syntax_to=_SYNTAX_XHTML, safe=True, profile=None):
+    def convert(
+        self, text, syntax_from, syntax_to=_SYNTAX_XHTML, safe=True, profile=None
+    ):
         """Convert a text between two syntaxes
 
         @param text: text to convert
@@ -235,7 +326,7 @@
         else:
             d = deferToThread(syntaxes[syntax_from]["to"], text)
 
-        #TODO: keep only body element and change it to a div here ?
+        # TODO: keep only body element and change it to a div here ?
 
         if safe:
             d.addCallback(self.cleanXHTML)
@@ -249,7 +340,7 @@
         d.addCallback(lambda text: text.rstrip())
         return d
 
-    def addSyntax(self, name, to_xhtml_cb, from_xhtml_cb, flags = None):
+    def addSyntax(self, name, to_xhtml_cb, from_xhtml_cb, flags=None):
         """Add a new syntax to the manager
 
         @param name: unique name of the syntax
@@ -262,13 +353,24 @@
         """
         flags = flags if flags is not None else []
         if TextSyntaxes.OPT_HIDDEN in flags and TextSyntaxes.OPT_DEFAULT in flags:
-            raise ValueError(u"{} and {} are mutually exclusive".format(TextSyntaxes.OPT_HIDDEN, TextSyntaxes.OPT_DEFAULT))
+            raise ValueError(
+                u"{} and {} are mutually exclusive".format(
+                    TextSyntaxes.OPT_HIDDEN, TextSyntaxes.OPT_DEFAULT
+                )
+            )
 
         syntaxes = TextSyntaxes.syntaxes
         key = name.lower().strip()
         if key in syntaxes:
-            raise exceptions.ConflictError(u"This syntax key already exists: {}".format(key))
-        syntaxes[key] = {"name": name, "to": to_xhtml_cb, "from": from_xhtml_cb, "flags": flags}
+            raise exceptions.ConflictError(
+                u"This syntax key already exists: {}".format(key)
+            )
+        syntaxes[key] = {
+            "name": name,
+            "to": to_xhtml_cb,
+            "from": from_xhtml_cb,
+            "flags": flags,
+        }
         if TextSyntaxes.OPT_DEFAULT in flags:
             TextSyntaxes.default_syntaxe = key
 
@@ -290,6 +392,6 @@
         @param xhtml: the XHTML string to be cleaned
         @return: the cleaned string
         """
-        cleaner = clean.Cleaner(kill_tags=['style'])
+        cleaner = clean.Cleaner(kill_tags=["style"])
         cleaned = cleaner.clean_html(html.fromstring(xhtml))
         return html.tostring(cleaned, encoding=unicode, method="text")
--- a/sat/plugins/plugin_misc_tickets.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_misc_tickets.py	Wed Jun 27 20:14:46 2018 +0200
@@ -24,9 +24,10 @@
 from sat.tools import utils
 import shortuuid
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 
-NS_TICKETS = 'org.salut-a-toi.tickets:0'
+NS_TICKETS = "org.salut-a-toi.tickets:0"
 
 PLUGIN_INFO = {
     C.PI_NAME: _("Tickets management"),
@@ -36,49 +37,84 @@
     C.PI_DEPENDENCIES: ["XEP-0060", "PUBSUB_SCHEMA", "XEP-0277", "IDENTITY"],
     C.PI_MAIN: "Tickets",
     C.PI_HANDLER: "no",
-    C.PI_DESCRIPTION: _("""Tickets management plugin""")
+    C.PI_DESCRIPTION: _("""Tickets management plugin"""),
 }
 
 
 class Tickets(object):
-
     def __init__(self, host):
         log.info(_(u"Tickets plugin initialization"))
         self.host = host
-        host.registerNamespace('tickets', NS_TICKETS)
+        host.registerNamespace("tickets", NS_TICKETS)
         self._p = self.host.plugins["XEP-0060"]
         self._s = self.host.plugins["PUBSUB_SCHEMA"]
         self._m = self.host.plugins["XEP-0277"]
-        host.bridge.addMethod("ticketsGet", ".plugin",
-                              in_sign='ssiassa{ss}s', out_sign='(asa{ss})',
-                              method=utils.partial(
-                                  self._s._get,
-                                  default_node=NS_TICKETS,
-                                  form_ns=NS_TICKETS,
-                                  filters = {u'author': self._s.valueOrPublisherFilter,
-                                             u'created': self._s.dateFilter,
-                                             u'updated': self._s.dateFilter,
-                                             }),
+        host.bridge.addMethod(
+            "ticketsGet",
+            ".plugin",
+            in_sign="ssiassa{ss}s",
+            out_sign="(asa{ss})",
+            method=utils.partial(
+                self._s._get,
+                default_node=NS_TICKETS,
+                form_ns=NS_TICKETS,
+                filters={
+                    u"author": self._s.valueOrPublisherFilter,
+                    u"created": self._s.dateFilter,
+                    u"updated": self._s.dateFilter,
+                },
+            ),
+            async=True,
+        )
+        host.bridge.addMethod(
+            "ticketSet",
+            ".plugin",
+            in_sign="ssa{sas}ssa{ss}s",
+            out_sign="s",
+            method=self._set,
+            async=True,
+        )
+        host.bridge.addMethod(
+            "ticketsSchemaGet",
+            ".plugin",
+            in_sign="sss",
+            out_sign="s",
+            method=utils.partial(self._s._getUISchema, default_node=NS_TICKETS),
+            async=True,
+        )
 
-                              async=True
-                              )
-        host.bridge.addMethod("ticketSet", ".plugin",
-                              in_sign='ssa{sas}ssa{ss}s', out_sign='s',
-                              method=self._set,
-                              async=True)
-        host.bridge.addMethod("ticketsSchemaGet", ".plugin",
-                              in_sign='sss', out_sign='s',
-                              method=utils.partial(self._s._getUISchema, default_node=NS_TICKETS),
-                              async=True)
-
-    def _set(self, service, node, values, schema=None, item_id=None, extra=None, profile_key=C.PROF_KEY_NONE):
-        client, service, node, schema, item_id, extra = self._s.prepareBridgeSet(service, node, schema, item_id, extra, profile_key)
-        d = self.set(client, service, node, values, schema, item_id, extra, deserialise=True)
-        d.addCallback(lambda ret: ret or u'')
+    def _set(
+        self,
+        service,
+        node,
+        values,
+        schema=None,
+        item_id=None,
+        extra=None,
+        profile_key=C.PROF_KEY_NONE,
+    ):
+        client, service, node, schema, item_id, extra = self._s.prepareBridgeSet(
+            service, node, schema, item_id, extra, profile_key
+        )
+        d = self.set(
+            client, service, node, values, schema, item_id, extra, deserialise=True
+        )
+        d.addCallback(lambda ret: ret or u"")
         return d
 
     @defer.inlineCallbacks
-    def set(self, client, service, node, values, schema=None, item_id=None, extra=None, deserialise=False, form_ns=NS_TICKETS):
+    def set(
+        self,
+        client,
+        service,
+        node,
+        values,
+        schema=None,
+        item_id=None,
+        extra=None,
+        deserialise=False,
+        form_ns=NS_TICKETS,
+    ):
         """Publish a tickets
 
         @param node(unicode, None): Pubsub node to use
@@ -102,15 +138,25 @@
             # we need to use uuid for comments node, because we don't know item id in advance
             # (we don't want to set it ourselves to let the server choose, so we can have
             #  a nicer id if serial ids is activated)
-            comments_node = self._m.getCommentsNode(node + u'_' + unicode(shortuuid.uuid()))
-            options = {self._p.OPT_ACCESS_MODEL: self._p.ACCESS_OPEN,
-                       self._p.OPT_PERSIST_ITEMS: 1,
-                       self._p.OPT_MAX_ITEMS: -1,
-                       self._p.OPT_DELIVER_PAYLOADS: 1,
-                       self._p.OPT_SEND_ITEM_SUBSCRIBE: 1,
-                       self._p.OPT_PUBLISH_MODEL: self._p.ACCESS_OPEN,
-                       }
+            comments_node = self._m.getCommentsNode(
+                node + u"_" + unicode(shortuuid.uuid())
+            )
+            options = {
+                self._p.OPT_ACCESS_MODEL: self._p.ACCESS_OPEN,
+                self._p.OPT_PERSIST_ITEMS: 1,
+                self._p.OPT_MAX_ITEMS: -1,
+                self._p.OPT_DELIVER_PAYLOADS: 1,
+                self._p.OPT_SEND_ITEM_SUBSCRIBE: 1,
+                self._p.OPT_PUBLISH_MODEL: self._p.ACCESS_OPEN,
+            }
             yield self._p.createNode(client, comments_service, comments_node, options)
-            values['comments_uri'] = uri.buildXMPPUri(u'pubsub', subtype='microblog', path=comments_service.full(), node=comments_node)
-        item_id = yield self._s.set(client, service, node, values, schema, item_id, extra, deserialise, form_ns)
+            values["comments_uri"] = uri.buildXMPPUri(
+                u"pubsub",
+                subtype="microblog",
+                path=comments_service.full(),
+                node=comments_node,
+            )
+        item_id = yield self._s.set(
+            client, service, node, values, schema, item_id, extra, deserialise, form_ns
+        )
         defer.returnValue(item_id)
--- a/sat/plugins/plugin_misc_upload.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_misc_upload.py	Wed Jun 27 20:14:46 2018 +0200
@@ -20,6 +20,7 @@
 from sat.core.i18n import _, D_
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from sat.core import exceptions
 from sat.tools import xml_tools
@@ -35,13 +36,13 @@
     C.PI_TYPE: C.PLUG_TYPE_MISC,
     C.PI_MAIN: "UploadPlugin",
     C.PI_HANDLER: "no",
-    C.PI_DESCRIPTION: _("""File upload management""")
+    C.PI_DESCRIPTION: _("""File upload management"""),
 }
 
 
-UPLOADING = D_(u'Please select a file to upload')
-UPLOADING_TITLE = D_(u'File upload')
-BOOL_OPTIONS = ('ignore_tls_errors',)
+UPLOADING = D_(u"Please select a file to upload")
+UPLOADING_TITLE = D_(u"File upload")
+BOOL_OPTIONS = ("ignore_tls_errors",)
 
 
 class UploadPlugin(object):
@@ -50,10 +51,19 @@
     def __init__(self, host):
         log.info(_("plugin Upload initialization"))
         self.host = host
-        host.bridge.addMethod("fileUpload", ".plugin", in_sign='sssa{ss}s', out_sign='a{ss}', method=self._fileUpload, async=True)
+        host.bridge.addMethod(
+            "fileUpload",
+            ".plugin",
+            in_sign="sssa{ss}s",
+            out_sign="a{ss}",
+            method=self._fileUpload,
+            async=True,
+        )
         self._upload_callbacks = []
 
-    def _fileUpload(self, filepath, filename, upload_jid_s='', options=None, profile=C.PROF_KEY_NONE):
+    def _fileUpload(
+        self, filepath, filename, upload_jid_s="", options=None, profile=C.PROF_KEY_NONE
+    ):
         client = self.host.getClient(profile)
         upload_jid = jid.JID(upload_jid_s) if upload_jid_s else None
         if options is None:
@@ -65,7 +75,9 @@
             except KeyError:
                 pass
 
-        return self.fileUpload(client, filepath, filename or None, upload_jid, options or None)
+        return self.fileUpload(
+            client, filepath, filename or None, upload_jid, options or None
+        )
 
     def fileUpload(self, client, filepath, filename, upload_jid, options):
         """Send a file using best available method
@@ -73,14 +85,19 @@
         parameters are the same as for [upload]
         @return (dict): action dictionary, with progress id in case of success, else xmlui message
         """
+
         def uploadCb(data):
             progress_id, dummy = data
-            return {'progress': progress_id}
+            return {"progress": progress_id}
 
         def uploadEb(fail):
             msg = unicode(fail)
             log.warning(msg)
-            return {'xmlui': xml_tools.note(u"Can't upload file", msg, C.XMLUI_DATA_LVL_WARNING).toXml()}
+            return {
+                "xmlui": xml_tools.note(
+                    u"Can't upload file", msg, C.XMLUI_DATA_LVL_WARNING
+                ).toXml()
+            }
 
         d = self.upload(client, filepath, filename, upload_jid, options)
         d.addCallback(uploadCb)
@@ -111,10 +128,14 @@
             try:
                 upload_jid = yield available_cb(upload_jid, client.profile)
             except exceptions.NotFound:
-                continue # no entity managing this extension found
+                continue  # no entity managing this extension found
 
-            log.info(u"{name} method will be used to upload the file".format(name=method_name))
-            progress_id_d, download_d = yield upload_cb(filepath, filename, upload_jid, options, client.profile)
+            log.info(
+                u"{name} method will be used to upload the file".format(name=method_name)
+            )
+            progress_id_d, download_d = yield upload_cb(
+                filepath, filename, upload_jid, options, client.profile
+            )
             progress_id = yield progress_id_d
             defer.returnValue((progress_id, download_d))
 
@@ -137,7 +158,9 @@
         assert method_name
         for data in self._upload_callbacks:
             if method_name == data[0]:
-                raise exceptions.ConflictError(u'A method with this name is already registered')
+                raise exceptions.ConflictError(
+                    u"A method with this name is already registered"
+                )
         self._upload_callbacks.append((method_name, available_cb, upload_cb, priority))
         self._upload_callbacks.sort(key=lambda data: data[3], reverse=True)
 
--- a/sat/plugins/plugin_misc_watched.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_misc_watched.py	Wed Jun 27 20:14:46 2018 +0200
@@ -20,6 +20,7 @@
 from sat.core.i18n import _, D_
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from sat.core import exceptions
 from sat.tools import xml_tools
@@ -33,7 +34,9 @@
     C.PI_DEPENDENCIES: [],
     C.PI_MAIN: "Watched",
     C.PI_HANDLER: "no",
-    C.PI_DESCRIPTION: _("""Watch for entities presence, and send notification accordingly""")
+    C.PI_DESCRIPTION: _(
+        """Watch for entities presence, and send notification accordingly"""
+    ),
 }
 
 
@@ -51,11 +54,9 @@
     </category>
     </individual>
     </params>
-    """.format(category_name=CATEGORY,
-               category_label=_(CATEGORY),
-               name=NAME,
-               label=_(NAME),
-              )
+    """.format(
+        category_name=CATEGORY, category_label=_(CATEGORY), name=NAME, label=_(NAME)
+    )
 
     def __init__(self, host):
         log.info(_("Watched initialisation"))
@@ -76,6 +77,13 @@
         if old_show == C.PRESENCE_UNAVAILABLE:
             watched = self.host.memory.getParamA(NAME, CATEGORY, profile_key=profile)
             if entity in watched or entity.userhostJID() in watched:
-                self.host.actionNew({'xmlui': xml_tools.note(_(NOTIF).format(entity=entity.full())).toXml()}, profile=profile)
+                self.host.actionNew(
+                    {
+                        "xmlui": xml_tools.note(
+                            _(NOTIF).format(entity=entity.full())
+                        ).toXml()
+                    },
+                    profile=profile,
+                )
 
         return True
--- a/sat/plugins/plugin_misc_welcome.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_misc_welcome.py	Wed Jun 27 20:14:46 2018 +0200
@@ -20,6 +20,7 @@
 from sat.core.i18n import _, D_
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from sat.tools import xml_tools
 
@@ -30,7 +31,9 @@
     C.PI_TYPE: C.PLUG_TYPE_MISC,
     C.PI_MAIN: "Welcome",
     C.PI_HANDLER: "no",
-    C.PI_DESCRIPTION: _("""Plugin which manage welcome message and things to to on first connection.""")
+    C.PI_DESCRIPTION: _(
+        """Plugin which manage welcome message and things to to on first connection."""
+    ),
 }
 
 
@@ -40,7 +43,8 @@
 WELCOME_MSG_TITLE = D_(u"Welcome to Libervia/Salut à Toi")
 # XXX: this message is mainly targetting libervia new users for now
 #      (i.e.: it may look weird on other frontends)
-WELCOME_MSG = D_(u"""Welcome to a free (as in freedom) network!
+WELCOME_MSG = D_(
+    u"""Welcome to a free (as in freedom) network!
 
 If you have any trouble, or you want to help us for the bug hunting, you can contact us in real time chat by using the “Help / Official chat room”  menu.
 
@@ -49,7 +53,8 @@
 We hope that you'll enjoy using this project.
 
 The Libervia/Salut à Toi Team
-""")
+"""
+)
 
 
 PARAMS = """
@@ -60,11 +65,12 @@
     </category>
     </individual>
     </params>
-    """.format(category=WELCOME_PARAM_CATEGORY, name=WELCOME_PARAM_NAME, label=WELCOME_PARAM_LABEL)
+    """.format(
+    category=WELCOME_PARAM_CATEGORY, name=WELCOME_PARAM_NAME, label=WELCOME_PARAM_LABEL
+)
 
 
 class Welcome(object):
-
     def __init__(self, host):
         log.info(_("plugin Welcome initialization"))
         self.host = host
@@ -73,7 +79,12 @@
     def profileConnected(self, client):
         # XXX: if you wan to try first_start again, you'll have to remove manually
         #      the welcome value from your profile params in sat.db
-        welcome = self.host.memory.params.getParamA(WELCOME_PARAM_NAME, WELCOME_PARAM_CATEGORY, use_default=False, profile_key=client.profile)
+        welcome = self.host.memory.params.getParamA(
+            WELCOME_PARAM_NAME,
+            WELCOME_PARAM_CATEGORY,
+            use_default=False,
+            profile_key=client.profile,
+        )
         if welcome is None:
             first_start = True
             welcome = True
@@ -82,12 +93,12 @@
 
         if welcome:
             xmlui = xml_tools.note(WELCOME_MSG, WELCOME_MSG_TITLE)
-            self.host.actionNew({'xmlui': xmlui.toXml()}, profile=client.profile)
-            self.host.memory.setParam(WELCOME_PARAM_NAME, C.BOOL_FALSE, WELCOME_PARAM_CATEGORY, profile_key=client.profile)
+            self.host.actionNew({"xmlui": xmlui.toXml()}, profile=client.profile)
+            self.host.memory.setParam(
+                WELCOME_PARAM_NAME,
+                C.BOOL_FALSE,
+                WELCOME_PARAM_CATEGORY,
+                profile_key=client.profile,
+            )
 
         self.host.trigger.point("WELCOME", first_start, welcome, client.profile)
-
-
-
-
-
--- a/sat/plugins/plugin_misc_xmllog.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_misc_xmllog.py	Wed Jun 27 20:14:46 2018 +0200
@@ -20,6 +20,7 @@
 from sat.core.i18n import _
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from twisted.words.xish import domish
 from twisted.words.xish import xmlstream
@@ -32,11 +33,12 @@
     C.PI_DEPENDENCIES: [],
     C.PI_MAIN: "XmlLog",
     C.PI_HANDLER: "no",
-    C.PI_DESCRIPTION: _(u"""Send raw XML logs to bridge""")
+    C.PI_DESCRIPTION: _(u"""Send raw XML logs to bridge"""),
 }
 
 host = None
 
+
 def send(self, obj):
     global host
     if isinstance(obj, basestring):
@@ -44,7 +46,7 @@
     elif isinstance(obj, domish.Element):
         log = obj.toXml()
     else:
-        log.error(_(u'INTERNAL ERROR: Unmanaged XML type'))
+        log.error(_(u"INTERNAL ERROR: Unmanaged XML type"))
     host.bridge.xmlLog("OUT", log, self._profile)
     return self._original_send(obj)
 
@@ -65,18 +67,22 @@
     </category>
     </general>
     </params>
-    """ % {"label_xmllog": _("Activate XML log")}
+    """ % {
+        "label_xmllog": _("Activate XML log")
+    }
 
     def __init__(self, host_):
         log.info(_("Plugin XML Log initialization"))
         global host
         host = host_
 
-        #parameters
+        # parameters
         host.memory.updateParams(self.params)
 
-        #bridge
-        host.bridge.addSignal("xmlLog", ".plugin", signature='sss')  # args: direction("IN" or "OUT"), xml_data, profile
+        # bridge
+        host.bridge.addSignal(
+            "xmlLog", ".plugin", signature="sss"
+        )  # args: direction("IN" or "OUT"), xml_data, profile
 
         self.do_log = host.memory.getParamA("Xml log", "Debug")
         if self.do_log:
@@ -85,7 +91,7 @@
             XmlStream._original_onElement = XmlStream.onElement
             XmlStream.send = send
             XmlStream.onElement = onElement
-            XmlStream._profile = ''
+            XmlStream._profile = ""
             host.trigger.add("XML Initialized", self.setProfile)
             log.info(_(u"XML log activated"))
 
--- a/sat/plugins/plugin_sec_otr.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_sec_otr.py	Wed Jun 27 20:14:46 2018 +0200
@@ -24,6 +24,7 @@
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
 from sat.core import exceptions
+
 log = getLogger(__name__)
 from sat.tools import xml_tools
 from twisted.words.protocols.jabber import jid
@@ -44,27 +45,27 @@
     C.PI_DEPENDENCIES: [u"XEP-0280", u"XEP-0334"],
     C.PI_MAIN: u"OTR",
     C.PI_HANDLER: u"no",
-    C.PI_DESCRIPTION: _(u"""Implementation of OTR""")
+    C.PI_DESCRIPTION: _(u"""Implementation of OTR"""),
 }
 
 NS_OTR = "otr_plugin"
 PRIVATE_KEY = "PRIVATE KEY"
-OTR_MENU = D_(u'OTR')
-AUTH_TXT = D_(u"To authenticate your correspondent, you need to give your below fingerprint *BY AN EXTERNAL CANAL* (i.e. not in this chat), and check that the one he gives you is the same as below. If there is a mismatch, there can be a spy between you!")
-DROP_TXT = D_(u"You private key is used to encrypt messages for your correspondent, nobody except you must know it, if you are in doubt, you should drop it!\n\nAre you sure you want to drop your private key?")
+OTR_MENU = D_(u"OTR")
+AUTH_TXT = D_(
+    u"To authenticate your correspondent, you need to give your below fingerprint *BY AN EXTERNAL CANAL* (i.e. not in this chat), and check that the one he gives you is the same as below. If there is a mismatch, there can be a spy between you!"
+)
+DROP_TXT = D_(
+    u"You private key is used to encrypt messages for your correspondent, nobody except you must know it, if you are in doubt, you should drop it!\n\nAre you sure you want to drop your private key?"
+)
 # NO_LOG_AND = D_(u"/!\\Your history is not logged anymore, and")   # FIXME: not used at the moment
 NO_ADV_FEATURES = D_(u"Some of advanced features are disabled !")
 
-DEFAULT_POLICY_FLAGS = {
-  'ALLOW_V1':False,
-  'ALLOW_V2':True,
-  'REQUIRE_ENCRYPTION':True,
-}
+DEFAULT_POLICY_FLAGS = {"ALLOW_V1": False, "ALLOW_V2": True, "REQUIRE_ENCRYPTION": True}
 
-OTR_STATE_TRUSTED = 'trusted'
-OTR_STATE_UNTRUSTED = 'untrusted'
-OTR_STATE_UNENCRYPTED = 'unencrypted'
-OTR_STATE_ENCRYPTED = 'encrypted'
+OTR_STATE_TRUSTED = "trusted"
+OTR_STATE_UNTRUSTED = "untrusted"
+OTR_STATE_UNENCRYPTED = "unencrypted"
+OTR_STATE_ENCRYPTED = "encrypted"
 
 
 class Context(potr.context.Context):
@@ -89,25 +90,25 @@
             message data when an encrypted message is going to be sent
         """
         assert isinstance(self.peer, jid.JID)
-        msg = msg_str.decode('utf-8')
+        msg = msg_str.decode("utf-8")
         client = self.user.client
-        log.debug(u'injecting encrypted message to {to}'.format(to=self.peer))
+        log.debug(u"injecting encrypted message to {to}".format(to=self.peer))
         if appdata is None:
             mess_data = {
-                         'from': client.jid,
-                         'to': self.peer,
-                         'uid': unicode(uuid.uuid4()),
-                         'message': {'': msg},
-                         'subject': {},
-                         'type': 'chat',
-                         'extra': {},
-                         'timestamp': time.time(),
-                        }
+                "from": client.jid,
+                "to": self.peer,
+                "uid": unicode(uuid.uuid4()),
+                "message": {"": msg},
+                "subject": {},
+                "type": "chat",
+                "extra": {},
+                "timestamp": time.time(),
+            }
             client.generateMessageXML(mess_data)
-            client.send(mess_data['xml'])
+            client.send(mess_data["xml"])
         else:
-            message_elt = appdata[u'xml']
-            assert message_elt.name == u'message'
+            message_elt = appdata[u"xml"]
+            assert message_elt.name == u"message"
             message_elt.addElement("body", content=msg)
 
     def setState(self, state):
@@ -117,8 +118,12 @@
         log.debug(u"setState: %s (old_state=%s)" % (state, old_state))
 
         if state == potr.context.STATE_PLAINTEXT:
-            feedback = _(u"/!\\ conversation with %(other_jid)s is now UNENCRYPTED") % {'other_jid': self.peer.full()}
-            self.host.bridge.otrState(OTR_STATE_UNENCRYPTED, self.peer.full(), client.profile)
+            feedback = _(u"/!\\ conversation with %(other_jid)s is now UNENCRYPTED") % {
+                "other_jid": self.peer.full()
+            }
+            self.host.bridge.otrState(
+                OTR_STATE_UNENCRYPTED, self.peer.full(), client.profile
+            )
         elif state == potr.context.STATE_ENCRYPTED:
             try:
                 trusted = self.getCurrentTrust()
@@ -127,18 +132,27 @@
             trusted_str = _(u"trusted") if trusted else _(u"untrusted")
 
             if old_state == potr.context.STATE_ENCRYPTED:
-                feedback = D_(u"{trusted} OTR conversation with {other_jid} REFRESHED").format(
-                    trusted = trusted_str,
-                    other_jid = self.peer.full())
+                feedback = D_(
+                    u"{trusted} OTR conversation with {other_jid} REFRESHED"
+                ).format(trusted=trusted_str, other_jid=self.peer.full())
             else:
-                feedback = D_(u"{trusted} encrypted OTR conversation started with {other_jid}\n{extra_info}").format(
-                    trusted = trusted_str,
-                    other_jid = self.peer.full(),
-                    extra_info = NO_ADV_FEATURES)
-            self.host.bridge.otrState(OTR_STATE_ENCRYPTED, self.peer.full(), client.profile)
+                feedback = D_(
+                    u"{trusted} encrypted OTR conversation started with {other_jid}\n{extra_info}"
+                ).format(
+                    trusted=trusted_str,
+                    other_jid=self.peer.full(),
+                    extra_info=NO_ADV_FEATURES,
+                )
+            self.host.bridge.otrState(
+                OTR_STATE_ENCRYPTED, self.peer.full(), client.profile
+            )
         elif state == potr.context.STATE_FINISHED:
-            feedback = D_(u"OTR conversation with {other_jid} is FINISHED").format(other_jid = self.peer.full())
-            self.host.bridge.otrState(OTR_STATE_UNENCRYPTED, self.peer.full(), client.profile)
+            feedback = D_(u"OTR conversation with {other_jid} is FINISHED").format(
+                other_jid=self.peer.full()
+            )
+            self.host.bridge.otrState(
+                OTR_STATE_UNENCRYPTED, self.peer.full(), client.profile
+            )
         else:
             log.error(D_(u"Unknown OTR state"))
             return
@@ -176,37 +190,42 @@
         log.debug(u"savePrivkey")
         if self.privkey is None:
             raise exceptions.InternalError(_(u"Save is called but privkey is None !"))
-        priv_key = self.privkey.serializePrivateKey().encode('hex')
+        priv_key = self.privkey.serializePrivateKey().encode("hex")
         d = self.host.memory.encryptValue(priv_key, self.client.profile)
+
         def save_encrypted_key(encrypted_priv_key):
             self.client._otr_data[PRIVATE_KEY] = encrypted_priv_key
+
         d.addCallback(save_encrypted_key)
 
     def loadTrusts(self):
-        trust_data = self.client._otr_data.get('trust', {})
+        trust_data = self.client._otr_data.get("trust", {})
         for jid_, jid_data in trust_data.iteritems():
             for fingerprint, trust_level in jid_data.iteritems():
-                log.debug(u'setting trust for {jid}: [{fingerprint}] = "{trust_level}"'.format(jid=jid_, fingerprint=fingerprint, trust_level=trust_level))
+                log.debug(
+                    u'setting trust for {jid}: [{fingerprint}] = "{trust_level}"'.format(
+                        jid=jid_, fingerprint=fingerprint, trust_level=trust_level
+                    )
+                )
                 self.trusts.setdefault(jid.JID(jid_), {})[fingerprint] = trust_level
 
     def saveTrusts(self):
         log.debug(u"saving trusts for {profile}".format(profile=self.client.profile))
-        log.debug(u"trusts = {}".format(self.client._otr_data['trust']))
-        self.client._otr_data.force('trust')
+        log.debug(u"trusts = {}".format(self.client._otr_data["trust"]))
+        self.client._otr_data.force("trust")
 
     def setTrust(self, other_jid, fingerprint, trustLevel):
         try:
-            trust_data = self.client._otr_data['trust']
+            trust_data = self.client._otr_data["trust"]
         except KeyError:
             trust_data = {}
-            self.client._otr_data['trust'] = trust_data
+            self.client._otr_data["trust"] = trust_data
         jid_data = trust_data.setdefault(other_jid.full(), {})
         jid_data[fingerprint] = trustLevel
         super(Account, self).setTrust(other_jid, fingerprint, trustLevel)
 
 
 class ContextManager(object):
-
     def __init__(self, host, client):
         self.host = host
         self.account = Account(host, client)
@@ -214,7 +233,9 @@
 
     def startContext(self, other_jid):
         assert isinstance(other_jid, jid.JID)
-        context = self.contexts.setdefault(other_jid, Context(self.host, self.account, other_jid))
+        context = self.contexts.setdefault(
+            other_jid, Context(self.host, self.account, other_jid)
+        )
         return context
 
     def getContextForUser(self, other):
@@ -225,23 +246,51 @@
 
 
 class OTR(object):
-
     def __init__(self, host):
         log.info(_(u"OTR plugin initialization"))
         self.host = host
         self.context_managers = {}
-        self.skipped_profiles = set()  # FIXME: OTR should not be skipped per profile, this need to be refactored
-        self._p_hints = host.plugins[u'XEP-0334']
-        self._p_carbons = host.plugins[u'XEP-0280']
+        self.skipped_profiles = (
+            set()
+        )  #  FIXME: OTR should not be skipped per profile, this need to be refactored
+        self._p_hints = host.plugins[u"XEP-0334"]
+        self._p_carbons = host.plugins[u"XEP-0280"]
         host.trigger.add("MessageReceived", self.MessageReceivedTrigger, priority=100000)
         host.trigger.add("sendMessage", self.sendMessageTrigger, priority=100000)
         host.trigger.add("sendMessageData", self._sendMessageDataTrigger)
-        host.bridge.addMethod("skipOTR", ".plugin", in_sign='s', out_sign='', method=self._skipOTR)  # FIXME: must be removed, must be done on per-message basis
-        host.bridge.addSignal("otrState", ".plugin", signature='sss')  # args: state, destinee_jid, profile
-        host.importMenu((OTR_MENU, D_(u"Start/Refresh")), self._otrStartRefresh, security_limit=0, help_string=D_(u"Start or refresh an OTR session"), type_=C.MENU_SINGLE)
-        host.importMenu((OTR_MENU, D_(u"End session")), self._otrSessionEnd, security_limit=0, help_string=D_(u"Finish an OTR session"), type_=C.MENU_SINGLE)
-        host.importMenu((OTR_MENU, D_(u"Authenticate")), self._otrAuthenticate, security_limit=0, help_string=D_(u"Authenticate user/see your fingerprint"), type_=C.MENU_SINGLE)
-        host.importMenu((OTR_MENU, D_(u"Drop private key")), self._dropPrivKey, security_limit=0, type_=C.MENU_SINGLE)
+        host.bridge.addMethod(
+            "skipOTR", ".plugin", in_sign="s", out_sign="", method=self._skipOTR
+        )  # FIXME: must be removed, must be done on per-message basis
+        host.bridge.addSignal(
+            "otrState", ".plugin", signature="sss"
+        )  # args: state, destinee_jid, profile
+        host.importMenu(
+            (OTR_MENU, D_(u"Start/Refresh")),
+            self._otrStartRefresh,
+            security_limit=0,
+            help_string=D_(u"Start or refresh an OTR session"),
+            type_=C.MENU_SINGLE,
+        )
+        host.importMenu(
+            (OTR_MENU, D_(u"End session")),
+            self._otrSessionEnd,
+            security_limit=0,
+            help_string=D_(u"Finish an OTR session"),
+            type_=C.MENU_SINGLE,
+        )
+        host.importMenu(
+            (OTR_MENU, D_(u"Authenticate")),
+            self._otrAuthenticate,
+            security_limit=0,
+            help_string=D_(u"Authenticate user/see your fingerprint"),
+            type_=C.MENU_SINGLE,
+        )
+        host.importMenu(
+            (OTR_MENU, D_(u"Drop private key")),
+            self._dropPrivKey,
+            security_limit=0,
+            type_=C.MENU_SINGLE,
+        )
         host.trigger.add("presenceReceived", self._presenceReceivedTrigger)
 
     def _skipOTR(self, profile):
@@ -263,8 +312,12 @@
         yield client._otr_data.load()
         encrypted_priv_key = client._otr_data.get(PRIVATE_KEY, None)
         if encrypted_priv_key is not None:
-            priv_key = yield self.host.memory.decryptValue(encrypted_priv_key, client.profile)
-            ctxMng.account.privkey = potr.crypt.PK.parsePrivateKey(priv_key.decode('hex'))[0]
+            priv_key = yield self.host.memory.decryptValue(
+                encrypted_priv_key, client.profile
+            )
+            ctxMng.account.privkey = potr.crypt.PK.parsePrivateKey(
+                priv_key.decode("hex")
+            )[0]
         else:
             ctxMng.account.privkey = None
         ctxMng.account.loadTrusts()
@@ -285,7 +338,7 @@
         """
         client = self.host.getClient(profile)
         try:
-            to_jid = jid.JID(menu_data['jid'])
+            to_jid = jid.JID(menu_data["jid"])
         except KeyError:
             log.error(_(u"jid key is not present !"))
             return defer.fail(exceptions.DataError)
@@ -298,9 +351,11 @@
         @param to_jid(jid.JID): jid to start encrypted session with
         """
         if not to_jid.resource:
-            to_jid.resource = self.host.memory.getMainResource(client, to_jid) # FIXME: temporary and unsecure, must be changed when frontends are refactored
+            to_jid.resource = self.host.memory.getMainResource(
+                client, to_jid
+            )  # FIXME: temporary and unsecure, must be changed when frontends are refactored
         otrctx = client._otr_context_manager.getContextForUser(to_jid)
-        query = otrctx.sendMessage(0, '?OTRv?')
+        query = otrctx.sendMessage(0, "?OTRv?")
         otrctx.inject(query)
 
     def _otrSessionEnd(self, menu_data, profile):
@@ -311,7 +366,7 @@
         """
         client = self.host.getClient(profile)
         try:
-            to_jid = jid.JID(menu_data['jid'])
+            to_jid = jid.JID(menu_data["jid"])
         except KeyError:
             log.error(_(u"jid key is not present !"))
             return defer.fail(exceptions.DataError)
@@ -321,7 +376,9 @@
     def endSession(self, client, to_jid):
         """End an OTR session"""
         if not to_jid.resource:
-            to_jid.resource = self.host.memory.getMainResource(client, to_jid) # FIXME: temporary and unsecure, must be changed when frontends are refactored
+            to_jid.resource = self.host.memory.getMainResource(
+                client, to_jid
+            )  # FIXME: temporary and unsecure, must be changed when frontends are refactored
         otrctx = client._otr_context_manager.getContextForUser(to_jid)
         otrctx.disconnect()
         return {}
@@ -334,7 +391,7 @@
         """
         client = self.host.getClient(profile)
         try:
-            to_jid = jid.JID(menu_data['jid'])
+            to_jid = jid.JID(menu_data["jid"])
         except KeyError:
             log.error(_(u"jid key is not present !"))
             return defer.fail(exceptions.DataError)
@@ -343,65 +400,94 @@
     def authenticate(self, client, to_jid):
         """Authenticate other user and see our own fingerprint"""
         if not to_jid.resource:
-            to_jid.resource = self.host.memory.getMainResource(client, to_jid) # FIXME: temporary and unsecure, must be changed when frontends are refactored
+            to_jid.resource = self.host.memory.getMainResource(
+                client, to_jid
+            )  # FIXME: temporary and unsecure, must be changed when frontends are refactored
         ctxMng = client._otr_context_manager
         otrctx = ctxMng.getContextForUser(to_jid)
         priv_key = ctxMng.account.privkey
 
         if priv_key is None:
             # we have no private key yet
-            dialog = xml_tools.XMLUI(C.XMLUI_DIALOG,
-                     dialog_opt = {C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_MESSAGE,
-                                   C.XMLUI_DATA_MESS: _(u"You have no private key yet, start an OTR conversation to have one"),
-                                   C.XMLUI_DATA_LVL: C.XMLUI_DATA_LVL_WARNING
-                                  },
-                     title = _(u"No private key"),
-                     )
-            return {'xmlui': dialog.toXml()}
+            dialog = xml_tools.XMLUI(
+                C.XMLUI_DIALOG,
+                dialog_opt={
+                    C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_MESSAGE,
+                    C.XMLUI_DATA_MESS: _(
+                        u"You have no private key yet, start an OTR conversation to have one"
+                    ),
+                    C.XMLUI_DATA_LVL: C.XMLUI_DATA_LVL_WARNING,
+                },
+                title=_(u"No private key"),
+            )
+            return {"xmlui": dialog.toXml()}
 
         other_fingerprint = otrctx.getCurrentKey()
 
         if other_fingerprint is None:
             # we have a private key, but not the fingerprint of our correspondent
-            dialog = xml_tools.XMLUI(C.XMLUI_DIALOG,
-                     dialog_opt = {C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_MESSAGE,
-                                   C.XMLUI_DATA_MESS: _(u"Your fingerprint is:\n{fingerprint}\n\nStart an OTR conversation to have your correspondent one.").format(fingerprint=priv_key),
-                                   C.XMLUI_DATA_LVL: C.XMLUI_DATA_LVL_INFO
-                                  },
-                     title = _(u"Fingerprint"),
-                     )
-            return {'xmlui': dialog.toXml()}
+            dialog = xml_tools.XMLUI(
+                C.XMLUI_DIALOG,
+                dialog_opt={
+                    C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_MESSAGE,
+                    C.XMLUI_DATA_MESS: _(
+                        u"Your fingerprint is:\n{fingerprint}\n\nStart an OTR conversation to have your correspondent one."
+                    ).format(fingerprint=priv_key),
+                    C.XMLUI_DATA_LVL: C.XMLUI_DATA_LVL_INFO,
+                },
+                title=_(u"Fingerprint"),
+            )
+            return {"xmlui": dialog.toXml()}
 
         def setTrust(raw_data, profile):
             # This method is called when authentication form is submited
             data = xml_tools.XMLUIResult2DataFormResult(raw_data)
-            if data['match'] == 'yes':
+            if data["match"] == "yes":
                 otrctx.setCurrentTrust(OTR_STATE_TRUSTED)
                 note_msg = _(u"Your correspondent {correspondent} is now TRUSTED")
-                self.host.bridge.otrState(OTR_STATE_TRUSTED, to_jid.full(), client.profile)
+                self.host.bridge.otrState(
+                    OTR_STATE_TRUSTED, to_jid.full(), client.profile
+                )
             else:
-                otrctx.setCurrentTrust('')
+                otrctx.setCurrentTrust("")
                 note_msg = _(u"Your correspondent {correspondent} is now UNTRUSTED")
-                self.host.bridge.otrState(OTR_STATE_UNTRUSTED, to_jid.full(), client.profile)
-            note = xml_tools.XMLUI(C.XMLUI_DIALOG, dialog_opt = {
-                                   C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_NOTE,
-                                   C.XMLUI_DATA_MESS: note_msg.format(correspondent=otrctx.peer)}
-                                   )
-            return {'xmlui': note.toXml()}
+                self.host.bridge.otrState(
+                    OTR_STATE_UNTRUSTED, to_jid.full(), client.profile
+                )
+            note = xml_tools.XMLUI(
+                C.XMLUI_DIALOG,
+                dialog_opt={
+                    C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_NOTE,
+                    C.XMLUI_DATA_MESS: note_msg.format(correspondent=otrctx.peer),
+                },
+            )
+            return {"xmlui": note.toXml()}
 
         submit_id = self.host.registerCallback(setTrust, with_data=True, one_shot=True)
         trusted = bool(otrctx.getCurrentTrust())
 
-        xmlui = xml_tools.XMLUI(C.XMLUI_FORM, title=_('Authentication (%s)') % to_jid.full(), submit_id=submit_id)
+        xmlui = xml_tools.XMLUI(
+            C.XMLUI_FORM,
+            title=_("Authentication (%s)") % to_jid.full(),
+            submit_id=submit_id,
+        )
         xmlui.addText(_(AUTH_TXT))
         xmlui.addDivider()
-        xmlui.addText(D_(u"Your own fingerprint is:\n{fingerprint}").format(fingerprint=priv_key))
-        xmlui.addText(D_(u"Your correspondent fingerprint should be:\n{fingerprint}").format(fingerprint=other_fingerprint))
-        xmlui.addDivider('blank')
-        xmlui.changeContainer('pairs')
-        xmlui.addLabel(D_(u'Is your correspondent fingerprint the same as here ?'))
-        xmlui.addList("match", [('yes', _('yes')),('no', _('no'))], ['yes' if trusted else 'no'])
-        return {'xmlui': xmlui.toXml()}
+        xmlui.addText(
+            D_(u"Your own fingerprint is:\n{fingerprint}").format(fingerprint=priv_key)
+        )
+        xmlui.addText(
+            D_(u"Your correspondent fingerprint should be:\n{fingerprint}").format(
+                fingerprint=other_fingerprint
+            )
+        )
+        xmlui.addDivider("blank")
+        xmlui.changeContainer("pairs")
+        xmlui.addLabel(D_(u"Is your correspondent fingerprint the same as here ?"))
+        xmlui.addList(
+            "match", [("yes", _("yes")), ("no", _("no"))], ["yes" if trusted else "no"]
+        )
+        return {"xmlui": xmlui.toXml()}
 
     def _dropPrivKey(self, menu_data, profile):
         """Drop our private Key
@@ -411,47 +497,69 @@
         """
         client = self.host.getClient(profile)
         try:
-            to_jid = jid.JID(menu_data['jid'])
+            to_jid = jid.JID(menu_data["jid"])
             if not to_jid.resource:
-                to_jid.resource = self.host.memory.getMainResource(client, to_jid) # FIXME: temporary and unsecure, must be changed when frontends are refactored
+                to_jid.resource = self.host.memory.getMainResource(
+                    client, to_jid
+                )  # FIXME: temporary and unsecure, must be changed when frontends are refactored
         except KeyError:
             log.error(_(u"jid key is not present !"))
             return defer.fail(exceptions.DataError)
 
         ctxMng = client._otr_context_manager
         if ctxMng.account.privkey is None:
-            return {'xmlui': xml_tools.note(_(u"You don't have a private key yet !")).toXml()}
+            return {
+                "xmlui": xml_tools.note(_(u"You don't have a private key yet !")).toXml()
+            }
 
         def dropKey(data, profile):
-            if C.bool(data['answer']):
+            if C.bool(data["answer"]):
                 # we end all sessions
                 for context in ctxMng.contexts.values():
                     context.disconnect()
                 ctxMng.account.privkey = None
                 ctxMng.account.getPrivkey()  # as account.privkey is None, getPrivkey will generate a new key, and save it
-                return {'xmlui': xml_tools.note(D_(u"Your private key has been dropped")).toXml()}
+                return {
+                    "xmlui": xml_tools.note(
+                        D_(u"Your private key has been dropped")
+                    ).toXml()
+                }
             return {}
 
         submit_id = self.host.registerCallback(dropKey, with_data=True, one_shot=True)
 
-        confirm = xml_tools.XMLUI(C.XMLUI_DIALOG, title=_(u'Confirm private key drop'), dialog_opt = {'type': C.XMLUI_DIALOG_CONFIRM, 'message': _(DROP_TXT)}, submit_id = submit_id)
-        return {'xmlui': confirm.toXml()}
+        confirm = xml_tools.XMLUI(
+            C.XMLUI_DIALOG,
+            title=_(u"Confirm private key drop"),
+            dialog_opt={"type": C.XMLUI_DIALOG_CONFIRM, "message": _(DROP_TXT)},
+            submit_id=submit_id,
+        )
+        return {"xmlui": confirm.toXml()}
 
     def _receivedTreatment(self, data, client):
-        from_jid = data['from']
+        from_jid = data["from"]
         log.debug(u"_receivedTreatment [from_jid = %s]" % from_jid)
         otrctx = client._otr_context_manager.getContextForUser(from_jid)
 
         try:
-            message = data['message'].itervalues().next() # FIXME: Q&D fix for message refactoring, message is now a dict
-            res = otrctx.receiveMessage(message.encode('utf-8'))
+            message = (
+                data["message"].itervalues().next()
+            )  # FIXME: Q&D fix for message refactoring, message is now a dict
+            res = otrctx.receiveMessage(message.encode("utf-8"))
         except potr.context.UnencryptedMessage:
             encrypted = False
             if otrctx.state == potr.context.STATE_ENCRYPTED:
-                log.warning(u"Received unencrypted message in an encrypted context (from {jid})".format(
-                    jid = from_jid.full()))
+                log.warning(
+                    u"Received unencrypted message in an encrypted context (from {jid})".format(
+                        jid=from_jid.full()
+                    )
+                )
 
-                feedback=D_(u"WARNING: received unencrypted data in a supposedly encrypted context"),
+                feedback = (
+                    D_(
+                        u"WARNING: received unencrypted data in a supposedly encrypted context"
+                    ),
+                )
                 client.feedback(from_jid, feedback)
         except StopIteration:
             return data
@@ -462,17 +570,25 @@
             if res[0] != None:
                 # decrypted messages handling.
                 # receiveMessage() will return a tuple, the first part of which will be the decrypted message
-                data['message'] = {'':res[0].decode('utf-8')} # FIXME: Q&D fix for message refactoring, message is now a dict
+                data["message"] = {
+                    "": res[0].decode("utf-8")
+                }  # FIXME: Q&D fix for message refactoring, message is now a dict
                 try:
                     # we want to keep message in history, even if no store is requested in message hints
-                    del data[u'history']
+                    del data[u"history"]
                 except KeyError:
                     pass
                 # TODO: add skip history as an option, but by default we don't skip it
                 # data[u'history'] = C.HISTORY_SKIP # we send the decrypted message to frontends, but we don't want it in history
             else:
-                log.warning(u"An encrypted message was expected, but got {}".format(data['message']))
-                raise failure.Failure(exceptions.CancelError('Cancelled by OTR')) # no message at all (no history, no signal)
+                log.warning(
+                    u"An encrypted message was expected, but got {}".format(
+                        data["message"]
+                    )
+                )
+                raise failure.Failure(
+                    exceptions.CancelError("Cancelled by OTR")
+                )  # no message at all (no history, no signal)
         return data
 
     def _receivedTreatmentForSkippedProfiles(self, data):
@@ -480,21 +596,23 @@
 
         but we still need to check if the message must be stored in history or not
         """
-        # XXX: FIXME: this should not be done on a per-profile basis, but  per-message
+        #  XXX: FIXME: this should not be done on a per-profile basis, but  per-message
         try:
-            message = data['message'].itervalues().next().encode('utf-8') # FIXME: Q&D fix for message refactoring, message is now a dict
+            message = (
+                data["message"].itervalues().next().encode("utf-8")
+            )  # FIXME: Q&D fix for message refactoring, message is now a dict
         except StopIteration:
             return data
         if message.startswith(potr.proto.OTRTAG):
-            # FIXME: it may be better to cancel the message and send it direclty to bridge
+            #  FIXME: it may be better to cancel the message and send it direclty to bridge
             #        this is used by Libervia, but this may send garbage message to other frontends
             #        if they are used at the same time as Libervia.
             #        Hard to avoid with decryption on Libervia though.
-            data[u'history'] = C.HISTORY_SKIP
+            data[u"history"] = C.HISTORY_SKIP
         return data
 
     def MessageReceivedTrigger(self, client, message_elt, post_treat):
-        if message_elt.getAttribute('type') == C.MESS_TYPE_GROUPCHAT:
+        if message_elt.getAttribute("type") == C.MESS_TYPE_GROUPCHAT:
             # OTR is not possible in group chats
             return True
         if client.profile in self.skipped_profiles:
@@ -504,51 +622,65 @@
         return True
 
     def _sendMessageDataTrigger(self, client, mess_data):
-        if not 'OTR' in mess_data:
+        if not "OTR" in mess_data:
             return
-        otrctx = mess_data['OTR']
-        message_elt = mess_data['xml']
-        to_jid = mess_data['to']
+        otrctx = mess_data["OTR"]
+        message_elt = mess_data["xml"]
+        to_jid = mess_data["to"]
         if otrctx.state == potr.context.STATE_ENCRYPTED:
             log.debug(u"encrypting message")
             body = None
             for child in list(message_elt.children):
-                if child.name == 'body':
+                if child.name == "body":
                     # we remove all unencrypted body,
                     # and will only encrypt the first one
                     if body is None:
                         body = child
                     message_elt.children.remove(child)
-                elif child.name == 'html':
+                elif child.name == "html":
                     # we don't want any XHTML-IM element
                     message_elt.children.remove(child)
             if body is None:
                 log.warning(u"No message found")
             else:
                 self._p_carbons.setPrivate(message_elt)
-                otrctx.sendMessage(0, unicode(body).encode('utf-8'), appdata=mess_data)
+                otrctx.sendMessage(0, unicode(body).encode("utf-8"), appdata=mess_data)
         else:
-            feedback = D_(u"Your message was not sent because your correspondent closed the encrypted conversation on his/her side. "
-                          u"Either close your own side, or refresh the session.")
+            feedback = D_(
+                u"Your message was not sent because your correspondent closed the encrypted conversation on his/her side. "
+                u"Either close your own side, or refresh the session."
+            )
             log.warning(_(u"Message discarded because closed encryption channel"))
             client.feedback(to_jid, feedback)
-            raise failure.Failure(exceptions.CancelError(u'Cancelled by OTR plugin'))
+            raise failure.Failure(exceptions.CancelError(u"Cancelled by OTR plugin"))
 
-    def sendMessageTrigger(self, client, mess_data, pre_xml_treatments, post_xml_treatments):
-        if mess_data['type'] == 'groupchat':
+    def sendMessageTrigger(
+        self, client, mess_data, pre_xml_treatments, post_xml_treatments
+    ):
+        if mess_data["type"] == "groupchat":
             return True
-        if client.profile in self.skipped_profiles:  # FIXME: should not be done on a per-profile basis
+        if (
+            client.profile in self.skipped_profiles
+        ):  #  FIXME: should not be done on a per-profile basis
             return True
-        to_jid = copy.copy(mess_data['to'])
+        to_jid = copy.copy(mess_data["to"])
         if not to_jid.resource:
-            to_jid.resource = self.host.memory.getMainResource(client, to_jid) # FIXME: full jid may not be known
+            to_jid.resource = self.host.memory.getMainResource(
+                client, to_jid
+            )  # FIXME: full jid may not be known
         otrctx = client._otr_context_manager.getContextForUser(to_jid)
         if otrctx.state != potr.context.STATE_PLAINTEXT:
             self._p_hints.addHint(mess_data, self._p_hints.HINT_NO_COPY)
             self._p_hints.addHint(mess_data, self._p_hints.HINT_NO_PERMANENT_STORE)
-            mess_data['OTR'] = otrctx  # this indicate that encryption is needed in sendMessageData trigger
-            if not mess_data['to'].resource:  # if not resource was given, we force it here
-                mess_data['to'] = to_jid
+            mess_data[
+                "OTR"
+            ] = (
+                otrctx
+            )  #  this indicate that encryption is needed in sendMessageData trigger
+            if not mess_data[
+                "to"
+            ].resource:  #  if not resource was given, we force it here
+                mess_data["to"] = to_jid
         return True
 
     def _presenceReceivedTrigger(self, entity, show, priority, statuses, profile):
@@ -557,7 +689,9 @@
         client = self.host.getClient(profile)
         if not entity.resource:
             try:
-                entity.resource = self.host.memory.getMainResource(client, entity)  # FIXME: temporary and unsecure, must be changed when frontends are refactored
+                entity.resource = self.host.memory.getMainResource(
+                    client, entity
+                )  # FIXME: temporary and unsecure, must be changed when frontends are refactored
             except exceptions.UnknownEntityError:
                 return True  #  entity was not connected
         if entity in client._otr_context_manager.contexts:
--- a/sat/plugins/plugin_syntax_wiki_dotclear.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_syntax_wiki_dotclear.py	Wed Jun 27 20:14:46 2018 +0200
@@ -21,6 +21,7 @@
 
 from sat.core.i18n import _
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from sat.core.constants import Const as C
 from sat.core import exceptions
@@ -36,61 +37,70 @@
     C.PI_DEPENDENCIES: ["TEXT-SYNTAXES"],
     C.PI_MAIN: "DCWikiSyntax",
     C.PI_HANDLER: "",
-    C.PI_DESCRIPTION: _("""Implementation of Dotclear wiki syntax""")
+    C.PI_DESCRIPTION: _("""Implementation of Dotclear wiki syntax"""),
 }
 
-NOTE_TPL = u'[{}]' # Note template
-NOTE_A_REV_TPL = u'rev_note_{}'
-NOTE_A_TPL = u'note_{}'
+NOTE_TPL = u"[{}]"  # Note template
+NOTE_A_REV_TPL = u"rev_note_{}"
+NOTE_A_TPL = u"note_{}"
 ESCAPE_CHARS_BASE = r"(?P<escape_char>[][{}%|\\/*#@{{}}~$-])"
-ESCAPE_CHARS_EXTRA = r"!?_+'()" # These chars are not escaped in XHTML => dc_wiki conversion,
-                                # but are used in the other direction
-ESCAPE_CHARS = ESCAPE_CHARS_BASE.format('')
-FLAG_UL = 'ul' # must be the name of the element
-FLAG_OL = 'ol'
-ELT_WITH_STYLE = ('img', 'div') # elements where a style attribute is expected
+ESCAPE_CHARS_EXTRA = (
+    r"!?_+'()"
+)  # These chars are not escaped in XHTML => dc_wiki conversion,
+# but are used in the other direction
+ESCAPE_CHARS = ESCAPE_CHARS_BASE.format("")
+FLAG_UL = "ul"  # must be the name of the element
+FLAG_OL = "ol"
+ELT_WITH_STYLE = ("img", "div")  # elements where a style attribute is expected
 
-wiki = [r'\\' + ESCAPE_CHARS_BASE.format(ESCAPE_CHARS_EXTRA),
-        r"^!!!!!(?P<h1_title>.+?)$",
-        r"^!!!!(?P<h2_title>.+?)$",
-        r"^!!!(?P<h3_title>.+?)$",
-        r"^!!(?P<h4_title>.+?)$",
-        r"^!(?P<h5_title>.+?)$",
-        r"^----$(?P<horizontal_rule>)",
-        r"^\*(?P<list_bullet>.*?)$",
-        r"^#(?P<list_ordered>.*?)$",
-        r"^ (?P<preformated>.*?)$",
-        r"^> +?(?P<quote>.*?)$",
-        r"''(?P<emphasis>.+?)''",
-        r"__(?P<strong_emphasis>.+?)__",
-        r"%%%(?P<line_break>)",
-        r"\+\+(?P<insertion>.+?)\+\+",
-        r"--(?P<deletion>.+?)--",
-        r"\[(?P<link>.+?)\]",
-        r"\(\((?P<image>.+?)\)\)",
-        r"~(?P<anchor>.+?)~",
-        r"\?\?(?P<acronym>.+?\|.+?)\?\?",
-        r"{{(?P<inline_quote>.+?)}}",
-        r"@@(?P<code>.+?)@@",
-        r"\$\$(?P<footnote>.+?)\$\$",
-        r"(?P<text>.+?)",
-       ]
+wiki = [
+    r"\\" + ESCAPE_CHARS_BASE.format(ESCAPE_CHARS_EXTRA),
+    r"^!!!!!(?P<h1_title>.+?)$",
+    r"^!!!!(?P<h2_title>.+?)$",
+    r"^!!!(?P<h3_title>.+?)$",
+    r"^!!(?P<h4_title>.+?)$",
+    r"^!(?P<h5_title>.+?)$",
+    r"^----$(?P<horizontal_rule>)",
+    r"^\*(?P<list_bullet>.*?)$",
+    r"^#(?P<list_ordered>.*?)$",
+    r"^ (?P<preformated>.*?)$",
+    r"^> +?(?P<quote>.*?)$",
+    r"''(?P<emphasis>.+?)''",
+    r"__(?P<strong_emphasis>.+?)__",
+    r"%%%(?P<line_break>)",
+    r"\+\+(?P<insertion>.+?)\+\+",
+    r"--(?P<deletion>.+?)--",
+    r"\[(?P<link>.+?)\]",
+    r"\(\((?P<image>.+?)\)\)",
+    r"~(?P<anchor>.+?)~",
+    r"\?\?(?P<acronym>.+?\|.+?)\?\?",
+    r"{{(?P<inline_quote>.+?)}}",
+    r"@@(?P<code>.+?)@@",
+    r"\$\$(?P<footnote>.+?)\$\$",
+    r"(?P<text>.+?)",
+]
 
-wiki_re = re.compile('|'.join(wiki), re.MULTILINE | re.DOTALL)
-wiki_block_level_re = re.compile(r"^///html(?P<html>.+?)///\n\n|(?P<paragraph>.+?)(?:\n{2,}|\Z)", re.MULTILINE | re.DOTALL)
+wiki_re = re.compile("|".join(wiki), re.MULTILINE | re.DOTALL)
+wiki_block_level_re = re.compile(
+    r"^///html(?P<html>.+?)///\n\n|(?P<paragraph>.+?)(?:\n{2,}|\Z)",
+    re.MULTILINE | re.DOTALL,
+)
 
 
 class DCWikiParser(object):
-
     def __init__(self):
         self._footnotes = None
         for i in xrange(5):
-            setattr(self,
-                'parser_h{}_title'.format(i),
-                lambda string, parent, i=i: self._parser_title(string, parent, 'h{}'.format(i)))
+            setattr(
+                self,
+                "parser_h{}_title".format(i),
+                lambda string, parent, i=i: self._parser_title(
+                    string, parent, "h{}".format(i)
+                ),
+            )
 
     def parser_paragraph(self, string, parent):
-        p_elt = parent.addElement('p')
+        p_elt = parent.addElement("p")
         self._parse(string, p_elt)
 
     def parser_html(self, string, parent):
@@ -101,7 +111,7 @@
             log.warning(u"Error while parsing HTML content, ignoring it: {}".format(e))
             return
         children = list(div_elt.elements())
-        if len(children) == 1 and children[0].name == 'div':
+        if len(children) == 1 and children[0].name == "div":
             div_elt = children[0]
         parent.addChild(div_elt)
 
@@ -113,128 +123,130 @@
         elt.addContent(string)
 
     def parser_horizontal_rule(self, string, parent):
-        parent.addElement('hr')
+        parent.addElement("hr")
 
     def _parser_list(self, string, parent, list_type):
         depth = 0
-        while string[depth:depth+1] == '*':
-            depth +=1
+        while string[depth : depth + 1] == "*":
+            depth += 1
 
         string = string[depth:].lstrip()
 
-        for i in xrange(depth+1):
+        for i in xrange(depth + 1):
             list_elt = getattr(parent, list_type)
             if not list_elt:
                 parent = parent.addElement(list_type)
             else:
                 parent = list_elt
 
-        li_elt = parent.addElement('li')
+        li_elt = parent.addElement("li")
         self._parse(string, li_elt)
 
     def parser_list_bullet(self, string, parent):
-        self._parser_list(string, parent, 'ul')
+        self._parser_list(string, parent, "ul")
 
     def parser_list_ordered(self, string, parent):
-        self._parser_list(string, parent, 'ol')
+        self._parser_list(string, parent, "ol")
 
     def parser_preformated(self, string, parent):
         pre_elt = parent.pre
         if pre_elt is None:
-            pre_elt = parent.addElement('pre')
+            pre_elt = parent.addElement("pre")
         else:
             # we are on a new line, and this is important for <pre/>
-            pre_elt.addContent('\n')
+            pre_elt.addContent("\n")
         pre_elt.addContent(string)
 
     def parser_quote(self, string, parent):
         blockquote_elt = parent.blockquote
         if blockquote_elt is None:
-            blockquote_elt = parent.addElement('blockquote')
+            blockquote_elt = parent.addElement("blockquote")
         p_elt = blockquote_elt.p
         if p_elt is None:
-            p_elt = blockquote_elt.addElement('p')
+            p_elt = blockquote_elt.addElement("p")
         else:
-            string = u'\n' + string
+            string = u"\n" + string
 
         self._parse(string, p_elt)
 
     def parser_emphasis(self, string, parent):
-        em_elt = parent.addElement('em')
+        em_elt = parent.addElement("em")
         self._parse(string, em_elt)
 
     def parser_strong_emphasis(self, string, parent):
-        strong_elt = parent.addElement('strong')
+        strong_elt = parent.addElement("strong")
         self._parse(string, strong_elt)
 
     def parser_line_break(self, string, parent):
-        parent.addElement('br')
+        parent.addElement("br")
 
     def parser_insertion(self, string, parent):
-        ins_elt = parent.addElement('ins')
+        ins_elt = parent.addElement("ins")
         self._parse(string, ins_elt)
 
     def parser_deletion(self, string, parent):
-        del_elt = parent.addElement('del')
+        del_elt = parent.addElement("del")
         self._parse(string, del_elt)
 
     def parser_link(self, string, parent):
-        url_data = string.split(u'|')
-        a_elt = parent.addElement('a')
+        url_data = string.split(u"|")
+        a_elt = parent.addElement("a")
         length = len(url_data)
         if length == 1:
             url = url_data[0]
-            a_elt['href'] = url
+            a_elt["href"] = url
             a_elt.addContent(url)
         else:
             name = url_data[0]
             url = url_data[1]
-            a_elt['href'] = url
+            a_elt["href"] = url
             a_elt.addContent(name)
             if length >= 3:
-                a_elt['lang'] = url_data[2]
+                a_elt["lang"] = url_data[2]
             if length >= 4:
-                a_elt['title'] = url_data[3]
+                a_elt["title"] = url_data[3]
             if length > 4:
                 log.warning(u"too much data for url, ignoring extra data")
 
     def parser_image(self, string, parent):
-        image_data = string.split(u'|')
-        img_elt = parent.addElement('img')
+        image_data = string.split(u"|")
+        img_elt = parent.addElement("img")
 
-        for idx, attribute in enumerate(('src', 'alt', 'position', 'longdesc')):
+        for idx, attribute in enumerate(("src", "alt", "position", "longdesc")):
             try:
                 data = image_data[idx]
             except IndexError:
                 break
 
-            if attribute != 'position':
+            if attribute != "position":
                 img_elt[attribute] = data
             else:
                 data = data.lower()
-                if data in ('l', 'g'):
-                    img_elt['style'] = "display:block; float:left; margin:0 1em 1em 0"
-                elif data in ('r', 'd'):
-                    img_elt['style'] = "display:block; float:right; margin:0 0 1em 1em"
-                elif data == 'c':
-                    img_elt['style'] = "display:block; margin-left:auto; margin-right:auto"
+                if data in ("l", "g"):
+                    img_elt["style"] = "display:block; float:left; margin:0 1em 1em 0"
+                elif data in ("r", "d"):
+                    img_elt["style"] = "display:block; float:right; margin:0 0 1em 1em"
+                elif data == "c":
+                    img_elt[
+                        "style"
+                    ] = "display:block; margin-left:auto; margin-right:auto"
                 else:
                     log.warning(u"bad position argument for image, ignoring it")
 
     def parser_anchor(self, string, parent):
-        a_elt = parent.addElement('a')
-        a_elt['id'] = string
+        a_elt = parent.addElement("a")
+        a_elt["id"] = string
 
     def parser_acronym(self, string, parent):
-        acronym, title = string.split(u'|',1)
-        acronym_elt = parent.addElement('acronym', content=acronym)
-        acronym_elt['title'] = title
+        acronym, title = string.split(u"|", 1)
+        acronym_elt = parent.addElement("acronym", content=acronym)
+        acronym_elt["title"] = title
 
     def parser_inline_quote(self, string, parent):
-        quote_data = string.split(u'|')
+        quote_data = string.split(u"|")
         quote = quote_data[0]
-        q_elt = parent.addElement('q', content=quote)
-        for idx, attribute in enumerate(('lang', 'cite'), 1):
+        q_elt = parent.addElement("q", content=quote)
+        for idx, attribute in enumerate(("lang", "cite"), 1):
             try:
                 data = quote_data[idx]
             except IndexError:
@@ -242,21 +254,21 @@
             q_elt[attribute] = data
 
     def parser_code(self, string, parent):
-        parent.addElement('code', content=string)
+        parent.addElement("code", content=string)
 
     def parser_footnote(self, string, parent):
         idx = len(self._footnotes) + 1
         note_txt = NOTE_TPL.format(idx)
-        sup_elt = parent.addElement('sup')
-        sup_elt['class'] = 'note'
-        a_elt = sup_elt.addElement('a', content=note_txt)
-        a_elt['id'] = NOTE_A_REV_TPL.format(idx)
-        a_elt['href'] = u'#{}'.format(NOTE_A_TPL.format(idx))
+        sup_elt = parent.addElement("sup")
+        sup_elt["class"] = "note"
+        a_elt = sup_elt.addElement("a", content=note_txt)
+        a_elt["id"] = NOTE_A_REV_TPL.format(idx)
+        a_elt["href"] = u"#{}".format(NOTE_A_TPL.format(idx))
 
-        p_elt = domish.Element((None, 'p'))
-        a_elt = p_elt.addElement('a', content=note_txt)
-        a_elt['id'] = NOTE_A_TPL.format(idx)
-        a_elt['href'] = u'#{}'.format(NOTE_A_REV_TPL.format(idx))
+        p_elt = domish.Element((None, "p"))
+        a_elt = p_elt.addElement("a", content=note_txt)
+        a_elt["id"] = NOTE_A_TPL.format(idx)
+        a_elt["href"] = u"#{}".format(NOTE_A_REV_TPL.format(idx))
         self._parse(string, p_elt)
         # footnotes are actually added at the end of the parsing
         self._footnotes.append(p_elt)
@@ -273,7 +285,7 @@
                 return
             matched = match.group(match.lastgroup)
             try:
-                parser = getattr(self, 'parser_{}'.format(match.lastgroup))
+                parser = getattr(self, "parser_{}".format(match.lastgroup))
             except AttributeError:
                 log.warning(u"No parser found for {}".format(match.lastgroup))
                 # parent.addContent(string)
@@ -282,46 +294,46 @@
 
     def parse(self, string):
         self._footnotes = []
-        div_elt = domish.Element((None, 'div'))
+        div_elt = domish.Element((None, "div"))
         self._parse(string, parent=div_elt, block_level=True)
         if self._footnotes:
-            foot_div_elt = div_elt.addElement('div')
-            foot_div_elt['class'] = 'footnotes'
+            foot_div_elt = div_elt.addElement("div")
+            foot_div_elt["class"] = "footnotes"
             # we add a simple horizontal rule which can be customized
             # with footnotes class, instead of a text which would need
             # to be translated
-            foot_div_elt.addElement('hr')
+            foot_div_elt.addElement("hr")
             for elt in self._footnotes:
                 foot_div_elt.addChild(elt)
         return div_elt
 
 
 class XHTMLParser(object):
-
     def __init__(self):
         self.flags = None
         self.toto = 0
-        self.footnotes = None # will hold a map from url to buffer id
-        for i in xrange(1,6):
-            setattr(self,
-                'parser_h{}'.format(i),
-                lambda elt, buf, level=i: self.parserHeading(elt, buf, level)
-                )
+        self.footnotes = None  # will hold a map from url to buffer id
+        for i in xrange(1, 6):
+            setattr(
+                self,
+                "parser_h{}".format(i),
+                lambda elt, buf, level=i: self.parserHeading(elt, buf, level),
+            )
 
     def parser_a(self, elt, buf):
         try:
-            url = elt['href']
+            url = elt["href"]
         except KeyError:
             # probably an anchor
             try:
-                id_ = elt['id']
+                id_ = elt["id"]
                 if not id_:
                     # we don't want empty values
                     raise KeyError
             except KeyError:
                 self.parserGeneric(elt, buf)
             else:
-                buf.append(u'~~{}~~'.format(id_))
+                buf.append(u"~~{}~~".format(id_))
             return
 
         link_data = [url]
@@ -329,52 +341,54 @@
         if name != url:
             link_data.insert(0, name)
 
-        lang = elt.getAttribute('lang')
-        title = elt.getAttribute('title')
+        lang = elt.getAttribute("lang")
+        title = elt.getAttribute("title")
         if lang is not None:
             link_data.append(lang)
         elif title is not None:
-            link_data.appand(u'')
+            link_data.appand(u"")
         if title is not None:
             link_data.append(title)
-        buf.append(u'[')
-        buf.append(u'|'.join(link_data))
-        buf.append(u']')
+        buf.append(u"[")
+        buf.append(u"|".join(link_data))
+        buf.append(u"]")
 
     def parser_acronym(self, elt, buf):
         try:
-            title = elt['title']
+            title = elt["title"]
         except KeyError:
             log.debug(u"Acronyme without title, using generic parser")
             self.parserGeneric(elt, buf)
             return
-        buf.append(u'??{}|{}??'.format(unicode(elt), title))
+        buf.append(u"??{}|{}??".format(unicode(elt), title))
 
     def parser_blockquote(self, elt, buf):
         # we remove wrapping <p> to avoid empty line with "> "
-        children = list([child for child in elt.children if unicode(child).strip() not in ('', '\n')])
-        if len(children) == 1 and children[0].name == 'p':
+        children = list(
+            [child for child in elt.children if unicode(child).strip() not in ("", "\n")]
+        )
+        if len(children) == 1 and children[0].name == "p":
             elt = children[0]
         tmp_buf = []
         self.parseChildren(elt, tmp_buf)
-        blockquote = u'> ' + u'\n> '.join(u''.join(tmp_buf).split('\n'))
+        blockquote = u"> " + u"\n> ".join(u"".join(tmp_buf).split("\n"))
         buf.append(blockquote)
 
     def parser_br(self, elt, buf):
-        buf.append(u'%%%')
+        buf.append(u"%%%")
 
     def parser_code(self, elt, buf):
-        buf.append(u'@@')
+        buf.append(u"@@")
         self.parseChildren(elt, buf)
-        buf.append(u'@@')
+        buf.append(u"@@")
 
     def parser_del(self, elt, buf):
-        buf.append(u'--')
+        buf.append(u"--")
         self.parseChildren(elt, buf)
-        buf.append(u'--')
+        buf.append(u"--")
 
     def parser_div(self, elt, buf):
-        if elt.getAttribute('class') == 'footnotes':
+        if elt.getAttribute("class") == "footnotes":
             self.parserFootnote(elt, buf)
         else:
             self.parseChildren(elt, buf, block=True)
@@ -387,56 +401,56 @@
     def parser_h6(self, elt, buf):
         # XXX: <h6/> heading is not managed by wiki syntax
         #      so we handle it with a <h5/>
-        elt = copy.copy(elt) # we don't want to change to original element
-        elt.name = 'h5'
+        elt = copy.copy(elt)  # we don't want to change to original element
+        elt.name = "h5"
         self._parse(elt, buf)
 
     def parser_hr(self, elt, buf):
-        buf.append(u'\n----\n')
+        buf.append(u"\n----\n")
 
     def parser_img(self, elt, buf):
         try:
-            url = elt['src']
+            url = elt["src"]
         except KeyError:
             log.warning(u"Ignoring <img/> without src")
             return
 
-        image_data=[url]
+        image_data = [url]
 
-        alt = elt.getAttribute('alt')
-        style = elt.getAttribute('style', '')
-        desc = elt.getAttribute('longdesc')
+        alt = elt.getAttribute("alt")
+        style = elt.getAttribute("style", "")
+        desc = elt.getAttribute("longdesc")
 
-        if '0 1em 1em 0' in style:
-            position = 'L'
-        elif '0 0 1em 1em' in style:
-            position = 'R'
-        elif 'auto' in style:
-            position = 'C'
+        if "0 1em 1em 0" in style:
+            position = "L"
+        elif "0 0 1em 1em" in style:
+            position = "R"
+        elif "auto" in style:
+            position = "C"
         else:
             position = None
 
         if alt:
             image_data.append(alt)
         elif position or desc:
-            image_data.append(u'')
+            image_data.append(u"")
 
         if position:
             image_data.append(position)
         elif desc:
-            image_data.append(u'')
+            image_data.append(u"")
 
         if desc:
             image_data.append(desc)
 
-        buf.append(u'((')
-        buf.append(u'|'.join(image_data))
-        buf.append(u'))')
+        buf.append(u"((")
+        buf.append(u"|".join(image_data))
+        buf.append(u"))")
 
     def parser_ins(self, elt, buf):
-        buf.append(u'++')
+        buf.append(u"++")
         self.parseChildren(elt, buf)
-        buf.append(u'++')
+        buf.append(u"++")
 
     def parser_li(self, elt, buf):
         flag = None
@@ -447,11 +461,11 @@
                 if current_flag is None:
                     current_flag = flag
                 if flag == current_flag:
-                    bullets.append(u'*' if flag == FLAG_UL else u'#')
+                    bullets.append(u"*" if flag == FLAG_UL else u"#")
                 else:
                     break
 
-        if flag != current_flag and buf[-1] == u' ':
+        if flag != current_flag and buf[-1] == u" ":
             # this trick is to avoid a space when we switch
             # from (un)ordered to the other type on the same row
             # e.g. *# unorder + ordered item
@@ -459,64 +473,75 @@
 
         buf.extend(bullets)
 
-        buf.append(u' ')
+        buf.append(u" ")
         self.parseChildren(elt, buf)
-        buf.append(u'\n')
+        buf.append(u"\n")
 
     def parser_ol(self, elt, buf):
         self.parserList(elt, buf, FLAG_OL)
 
     def parser_p(self, elt, buf):
         self.parseChildren(elt, buf)
-        buf.append(u'\n\n')
+        buf.append(u"\n\n")
 
     def parser_pre(self, elt, buf):
-        pre = u''.join([child.toXml() if domish.IElement.providedBy(child) else unicode(child) for child in elt.children])
-        pre = u' ' + u'\n '.join(pre.split('\n'))
+        pre = u"".join(
+            [
+                child.toXml() if domish.IElement.providedBy(child) else unicode(child)
+                for child in elt.children
+            ]
+        )
+        pre = u" " + u"\n ".join(pre.split("\n"))
         buf.append(pre)
 
     def parser_q(self, elt, buf):
-        quote_data=[unicode(elt)]
+        quote_data = [unicode(elt)]
 
-        lang = elt.getAttribute('lang')
-        cite = elt.getAttribute('url')
+        lang = elt.getAttribute("lang")
+        cite = elt.getAttribute("url")
 
         if lang:
             quote_data.append(lang)
         elif cite:
-            quote_data.append(u'')
+            quote_data.append(u"")
 
         if cite:
             quote_data.append(cite)
 
-        buf.append(u'{{')
-        buf.append(u'|'.join(quote_data))
-        buf.append(u'}}')
+        buf.append(u"{{")
+        buf.append(u"|".join(quote_data))
+        buf.append(u"}}")
 
     def parser_span(self, elt, buf):
         self.parseChildren(elt, buf, block=True)
 
     def parser_strong(self, elt, buf):
-        buf.append(u'__')
+        buf.append(u"__")
         self.parseChildren(elt, buf)
-        buf.append(u'__')
+        buf.append(u"__")
 
     def parser_sup(self, elt, buf):
         # sup is mainly used for footnotes, so we check if we have an anchor inside
-        children = list([child for child in elt.children if unicode(child).strip() not in ('', '\n')])
-        if (len(children) == 1 and domish.IElement.providedBy(children[0])
-            and children[0].name == 'a' and '#' in children[0].getAttribute('href', '')):
-            url = children[0]['href']
-            note_id = url[url.find('#')+1:]
+        children = list(
+            [child for child in elt.children if unicode(child).strip() not in ("", "\n")]
+        )
+        if (
+            len(children) == 1
+            and domish.IElement.providedBy(children[0])
+            and children[0].name == "a"
+            and "#" in children[0].getAttribute("href", "")
+        ):
+            url = children[0]["href"]
+            note_id = url[url.find("#") + 1 :]
             if not note_id:
                 log.warning("bad link found in footnote")
                 self.parserGeneric(elt, buf)
                 return
             # this looks like a footnote
-            buf.append(u'$$')
-            buf.append(u' ') # placeholder
+            buf.append(u"$$")
+            buf.append(u" ")  # placeholder
             self.footnotes[note_id] = len(buf) - 1
-            buf.append(u'$$')
+            buf.append(u"$$")
         else:
             self.parserGeneric(elt, buf)
 
@@ -537,39 +562,41 @@
             raise exceptions.InternalError(u"flag has been removed by an other parser")
 
     def parserHeading(self, elt, buf, level):
-        buf.append((6-level) * u'!')
+        buf.append((6 - level) * u"!")
         for child in elt.children:
             # we ignore other elements for a Hx title
             self.parserText(child, buf)
-        buf.append(u'\n')
+        buf.append(u"\n")
 
     def parserFootnote(self, elt, buf):
         for elt in elt.elements():
             # all children other than <p/> are ignored
-            if elt.name == 'p':
+            if elt.name == "p":
                 a_elt = elt.a
                 if a_elt is None:
-                    log.warning(u"<p/> element doesn't contain <a/> in footnote, ignoring it")
+                    log.warning(
+                        u"<p/> element doesn't contain <a/> in footnote, ignoring it"
+                    )
                     continue
                 try:
-                    note_idx = self.footnotes[a_elt['id']]
+                    note_idx = self.footnotes[a_elt["id"]]
                 except KeyError:
                     log.warning(u"Note id doesn't match any known note, ignoring it")
                 # we create a dummy element to parse all children after the <a/>
-                dummy_elt = domish.Element((None, 'note'))
+                dummy_elt = domish.Element((None, "note"))
                 a_idx = elt.children.index(a_elt)
-                dummy_elt.children = elt.children[a_idx+1:]
+                dummy_elt.children = elt.children[a_idx + 1 :]
                 note_buf = []
                 self.parseChildren(dummy_elt, note_buf)
                 # now we can replace the placeholder
-                buf[note_idx] = u''.join(note_buf)
+                buf[note_idx] = u"".join(note_buf)
 
     def parserText(self, txt, buf, keep_whitespaces=False):
         txt = unicode(txt)
         if not keep_whitespaces:
             # we get text and only let one inter word space
-            txt = u' '.join(txt.split())
-        txt = re.sub(ESCAPE_CHARS, r'\\\1', txt)
+            txt = u" ".join(txt.split())
+        txt = re.sub(ESCAPE_CHARS, r"\\\1", txt)
         if txt:
             buf.append(txt)
         return txt
@@ -582,9 +609,9 @@
     def parseChildren(self, elt, buf, block=False):
         first_visible = True
         for child in elt.children:
-            if not block and not first_visible and buf and buf[-1][-1] not in (' ','\n'):
+            if not block and not first_visible and buf and buf[-1][-1] not in (" ", "\n"):
                 # we add separation if it isn't already there
-                buf.append(u' ')
+                buf.append(u" ")
             if domish.IElement.providedBy(child):
                 self._parse(child, buf)
                 first_visible = False
@@ -595,7 +622,7 @@
 
     def _parse(self, elt, buf):
         elt_name = elt.name.lower()
-        style = elt.getAttribute('style')
+        style = elt.getAttribute("style")
         if style and elt_name not in ELT_WITH_STYLE:
             # if we have style we use generic parser to put raw HTML
             # to avoid losing it
@@ -604,7 +631,9 @@
             try:
                 parser = getattr(self, "parser_{}".format(elt_name))
             except AttributeError:
-                log.debug("Can't find parser for {} element, using generic one".format(elt.name))
+                log.debug(
+                    "Can't find parser for {} element, using generic one".format(elt.name)
+                )
                 parser = self.parserGeneric
         parser(elt, buf)
 
@@ -613,7 +642,7 @@
         self.footnotes = {}
         buf = []
         self._parse(elt, buf)
-        return u''.join(buf)
+        return u"".join(buf)
 
     def parseString(self, string):
         wrapped_html = u"<div>{}</div>".format(string)
@@ -623,7 +652,7 @@
             log.warning(u"Error while parsing HTML content: {}".format(e))
             return
         children = list(div_elt.elements())
-        if len(children) == 1 and children[0].name == 'div':
+        if len(children) == 1 and children[0].name == "div":
             div_elt = children[0]
         return self.parse(div_elt)
 
@@ -637,7 +666,9 @@
         self._dc_parser = DCWikiParser()
         self._xhtml_parser = XHTMLParser()
         self._stx = self.host.plugins["TEXT-SYNTAXES"]
-        self._stx.addSyntax(self.SYNTAX_NAME, self.parseWiki, self.parseXHTML, [self._stx.OPT_NO_THREAD])
+        self._stx.addSyntax(
+            self.SYNTAX_NAME, self.parseWiki, self.parseXHTML, [self._stx.OPT_NO_THREAD]
+        )
 
     def parseWiki(self, wiki_stx):
         div_elt = self._dc_parser.parse(wiki_stx)
--- a/sat/plugins/plugin_tickets_import.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_tickets_import.py	Wed Jun 27 20:14:46 2018 +0200
@@ -21,6 +21,7 @@
 from sat.core.constants import Const as C
 from sat.core import exceptions
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from twisted.internet import defer
 from sat.tools.common import uri
@@ -34,15 +35,17 @@
     C.PI_DEPENDENCIES: ["IMPORT", "XEP-0060", "XEP-0277", "PUBSUB_SCHEMA"],
     C.PI_MAIN: "TicketsImportPlugin",
     C.PI_HANDLER: "no",
-    C.PI_DESCRIPTION: _(u"""Tickets import management:
-This plugin manage the different tickets importers which can register to it, and handle generic importing tasks.""")
+    C.PI_DESCRIPTION: _(
+        u"""Tickets import management:
+This plugin manage the different tickets importers which can register to it, and handle generic importing tasks."""
+    ),
 }
 
-OPT_MAPPING = 'mapping'
-FIELDS_LIST = (u'labels', u'cc_emails')  # fields which must have a list as value
-FIELDS_DATE = (u'created', u'updated')
+OPT_MAPPING = "mapping"
+FIELDS_LIST = (u"labels", u"cc_emails")  # fields which must have a list as value
+FIELDS_DATE = (u"created", u"updated")
 
-NS_TICKETS = 'org.salut-a-toi.tickets:0'
+NS_TICKETS = "org.salut-a-toi.tickets:0"
 
 
 class TicketsImportPlugin(object):
@@ -54,13 +57,15 @@
         log.info(_("plugin Tickets Import initialization"))
         self.host = host
         self._importers = {}
-        self._p = host.plugins['XEP-0060']
-        self._m = host.plugins['XEP-0277']
-        self._s = host.plugins['PUBSUB_SCHEMA']
-        host.plugins['IMPORT'].initialize(self, u'tickets')
+        self._p = host.plugins["XEP-0060"]
+        self._m = host.plugins["XEP-0277"]
+        self._s = host.plugins["PUBSUB_SCHEMA"]
+        host.plugins["IMPORT"].initialize(self, u"tickets")
 
     @defer.inlineCallbacks
-    def importItem(self, client, item_import_data, session, options, return_data, service, node):
+    def importItem(
+        self, client, item_import_data, session, options, return_data, service, node
+    ):
         """
 
         @param item_import_data(dict): no key is mandatory, but if a key doesn't exists in dest form, it will be ignored.
@@ -101,20 +106,24 @@
                     If you specify several import ticket key to the same dest key,
                     the values will be joined with line feeds
         """
-        if 'comments_uri' in item_import_data:
-            raise exceptions.DataError(_(u'comments_uri key will be generated and must not be used by importer'))
+        if "comments_uri" in item_import_data:
+            raise exceptions.DataError(
+                _(u"comments_uri key will be generated and must not be used by importer")
+            )
         for key in FIELDS_LIST:
             if not isinstance(item_import_data.get(key, []), list):
-                raise exceptions.DataError(_(u'{key} must be a list').format(key=key))
+                raise exceptions.DataError(_(u"{key} must be a list").format(key=key))
         for key in FIELDS_DATE:
             try:
                 item_import_data[key] = utils.xmpp_date(item_import_data[key])
             except KeyError:
                 continue
-        if session[u'root_node'] is None:
-            session[u'root_node'] = NS_TICKETS
-        if not 'schema' in session:
-            session['schema'] = yield self._s.getSchemaForm(client, service, node or session[u'root_node'])
+        if session[u"root_node"] is None:
+            session[u"root_node"] = NS_TICKETS
+        if not "schema" in session:
+            session["schema"] = yield self._s.getSchemaForm(
+                client, service, node or session[u"root_node"]
+            )
         defer.returnValue(item_import_data)
 
     @defer.inlineCallbacks
@@ -122,40 +131,53 @@
         # TODO: force "open" permission (except if private, check below)
         # TODO: handle "private" metadata, to have non public access for node
         # TODO: node access/publish model should be customisable
-        comments = ticket_data.get('comments', [])
+        comments = ticket_data.get("comments", [])
         service = yield self._m.getCommentsService(client)
-        node = self._m.getCommentsNode(session['root_node'] + u'_' + ticket_data['id'])
-        node_options = {self._p.OPT_ACCESS_MODEL: self._p.ACCESS_OPEN,
-                        self._p.OPT_PERSIST_ITEMS: 1,
-                        self._p.OPT_MAX_ITEMS: -1,
-                        self._p.OPT_DELIVER_PAYLOADS: 1,
-                        self._p.OPT_SEND_ITEM_SUBSCRIBE: 1,
-                        self._p.OPT_PUBLISH_MODEL: self._p.ACCESS_OPEN,
-                        }
+        node = self._m.getCommentsNode(session["root_node"] + u"_" + ticket_data["id"])
+        node_options = {
+            self._p.OPT_ACCESS_MODEL: self._p.ACCESS_OPEN,
+            self._p.OPT_PERSIST_ITEMS: 1,
+            self._p.OPT_MAX_ITEMS: -1,
+            self._p.OPT_DELIVER_PAYLOADS: 1,
+            self._p.OPT_SEND_ITEM_SUBSCRIBE: 1,
+            self._p.OPT_PUBLISH_MODEL: self._p.ACCESS_OPEN,
+        }
         yield self._p.createIfNewNode(client, service, node, options=node_options)
-        ticket_data['comments_uri'] = uri.buildXMPPUri(u'pubsub', subtype='microblog', path=service.full(), node=node)
+        ticket_data["comments_uri"] = uri.buildXMPPUri(
+            u"pubsub", subtype="microblog", path=service.full(), node=node
+        )
         for comment in comments:
-            if 'updated' not in comment and 'published' in comment:
+            if "updated" not in comment and "published" in comment:
                 # we don't want an automatic update date
-                comment['updated'] = comment['published']
+                comment["updated"] = comment["published"]
             yield self._m.send(client, comment, service, node)
 
     def publishItem(self, client, ticket_data, service, node, session):
         if node is None:
             node = NS_TICKETS
-        id_ = ticket_data.pop('id', None)
-        log.debug(u"uploading item [{id}]: {title}".format(id=id_, title=ticket_data.get('title','')))
-        return self._s.sendDataFormItem(client, service, node, ticket_data, session['schema'], id_)
+        id_ = ticket_data.pop("id", None)
+        log.debug(
+            u"uploading item [{id}]: {title}".format(
+                id=id_, title=ticket_data.get("title", "")
+            )
+        )
+        return self._s.sendDataFormItem(
+            client, service, node, ticket_data, session["schema"], id_
+        )
 
     def itemFilters(self, client, ticket_data, session, options):
         mapping = options.get(OPT_MAPPING)
         if mapping is not None:
             if not isinstance(mapping, dict):
-                raise exceptions.DataError(_(u'mapping option must be a dictionary'))
+                raise exceptions.DataError(_(u"mapping option must be a dictionary"))
 
             for source, dest in mapping.iteritems():
                 if not isinstance(source, unicode) or not isinstance(dest, unicode):
-                    raise exceptions.DataError(_(u'keys and values of mapping must be sources and destinations ticket fields'))
+                    raise exceptions.DataError(
+                        _(
+                            u"keys and values of mapping must be sources and destinations ticket fields"
+                        )
+                    )
                 if source in ticket_data:
                     value = ticket_data.pop(source)
                     if dest in FIELDS_LIST:
@@ -163,6 +185,6 @@
                         values.append(value)
                     else:
                         if dest in ticket_data:
-                            ticket_data[dest] = ticket_data[dest] + u'\n' + value
+                            ticket_data[dest] = ticket_data[dest] + u"\n" + value
                         else:
                             ticket_data[dest] = value
--- a/sat/plugins/plugin_tickets_import_bugzilla.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_tickets_import_bugzilla.py	Wed Jun 27 20:14:46 2018 +0200
@@ -20,8 +20,10 @@
 from sat.core.i18n import _, D_
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from sat.core import exceptions
+
 # from twisted.internet import threads
 from twisted.internet import defer
 import os.path
@@ -36,25 +38,27 @@
     C.PI_DEPENDENCIES: ["TICKETS_IMPORT"],
     C.PI_MAIN: "BugzillaImport",
     C.PI_HANDLER: "no",
-    C.PI_DESCRIPTION: _("""Tickets importer for Bugzilla""")
+    C.PI_DESCRIPTION: _("""Tickets importer for Bugzilla"""),
 }
 
 SHORT_DESC = D_(u"import tickets from Bugzilla xml export file")
 
-LONG_DESC = D_(u"""This importer handle Bugzilla xml export file.
+LONG_DESC = D_(
+    u"""This importer handle Bugzilla xml export file.
 
 To use it, you'll need to export tickets using XML.
 Tickets will be uploaded with the same ID as for Bugzilla, any existing ticket with this ID will be replaced.
 
 location: you must use the absolute path to your .xml file
-""")
+"""
+)
 
 STATUS_MAP = {
-    'NEW': 'queued',
-    'ASSIGNED': 'started',
-    'RESOLVED': 'review',
-    'CLOSED': 'closed',
-    'REOPENED': 'started'  # we loose data here because there is no need on basic workflow to have a reopened status
+    "NEW": "queued",
+    "ASSIGNED": "started",
+    "RESOLVED": "review",
+    "CLOSED": "closed",
+    "REOPENED": "started",  # we loose data here because there is no need on basic workflow to have a reopened status
 }
 
 
@@ -65,67 +69,73 @@
         tickets = []
         root = etree.parse(file_path)
 
-        for bug in root.xpath('bug'):
+        for bug in root.xpath("bug"):
             ticket = {}
-            ticket['id'] = bug.findtext('bug_id')
-            ticket['created'] = date_utils.date_parse(bug.findtext('creation_ts'))
-            ticket['updated'] = date_utils.date_parse(bug.findtext('delta_ts'))
-            ticket['title'] = bug.findtext('short_desc')
-            reporter_elt = bug.find('reporter')
-            ticket['author'] = reporter_elt.get('name')
-            if ticket['author'] is None:
-                if '@' in reporter_elt.text:
-                    ticket['author'] = reporter_elt.text[:reporter_elt.text.find('@')].title()
+            ticket["id"] = bug.findtext("bug_id")
+            ticket["created"] = date_utils.date_parse(bug.findtext("creation_ts"))
+            ticket["updated"] = date_utils.date_parse(bug.findtext("delta_ts"))
+            ticket["title"] = bug.findtext("short_desc")
+            reporter_elt = bug.find("reporter")
+            ticket["author"] = reporter_elt.get("name")
+            if ticket["author"] is None:
+                if "@" in reporter_elt.text:
+                    ticket["author"] = reporter_elt.text[
+                        : reporter_elt.text.find("@")
+                    ].title()
                 else:
-                    ticket['author'] = u'no name'
-            ticket['author_email'] = reporter_elt.text
-            assigned_to_elt = bug.find('assigned_to')
-            ticket['assigned_to_name'] = assigned_to_elt.get('name')
-            ticket['assigned_to_email'] = assigned_to_elt.text
-            ticket['cc_emails'] = [e.text for e in bug.findall('cc')]
-            ticket['priority'] = bug.findtext('priority').lower().strip()
-            ticket['severity'] = bug.findtext('bug_severity').lower().strip()
-            ticket['product'] = bug.findtext('product')
-            ticket['component'] = bug.findtext('component')
-            ticket['version'] = bug.findtext('version')
-            ticket['platform'] = bug.findtext('rep_platform')
-            ticket['os'] = bug.findtext('op_sys')
-            ticket['status'] = STATUS_MAP.get(bug.findtext('bug_status'), 'queued')
-            ticket['milestone'] = bug.findtext('target_milestone')
-
+                    ticket["author"] = u"no name"
+            ticket["author_email"] = reporter_elt.text
+            assigned_to_elt = bug.find("assigned_to")
+            ticket["assigned_to_name"] = assigned_to_elt.get("name")
+            ticket["assigned_to_email"] = assigned_to_elt.text
+            ticket["cc_emails"] = [e.text for e in bug.findall("cc")]
+            ticket["priority"] = bug.findtext("priority").lower().strip()
+            ticket["severity"] = bug.findtext("bug_severity").lower().strip()
+            ticket["product"] = bug.findtext("product")
+            ticket["component"] = bug.findtext("component")
+            ticket["version"] = bug.findtext("version")
+            ticket["platform"] = bug.findtext("rep_platform")
+            ticket["os"] = bug.findtext("op_sys")
+            ticket["status"] = STATUS_MAP.get(bug.findtext("bug_status"), "queued")
+            ticket["milestone"] = bug.findtext("target_milestone")
 
             body = None
             comments = []
-            for longdesc in bug.findall('long_desc'):
+            for longdesc in bug.findall("long_desc"):
                 if body is None:
-                    body = longdesc.findtext('thetext')
+                    body = longdesc.findtext("thetext")
                 else:
-                    who = longdesc.find('who')
-                    comment = {'id': longdesc.findtext('commentid'),
-                               'author_email': who.text,
-                               'published': date_utils.date_parse(longdesc.findtext('bug_when')),
-                               'author': who.get('name', who.text),
-                               'content': longdesc.findtext('thetext')}
+                    who = longdesc.find("who")
+                    comment = {
+                        "id": longdesc.findtext("commentid"),
+                        "author_email": who.text,
+                        "published": date_utils.date_parse(longdesc.findtext("bug_when")),
+                        "author": who.get("name", who.text),
+                        "content": longdesc.findtext("thetext"),
+                    }
                     comments.append(comment)
 
-            ticket['body'] = body
-            ticket['comments'] = comments
+            ticket["body"] = body
+            ticket["comments"] = comments
             tickets.append(ticket)
 
-        tickets.sort(key = lambda t: int(t['id']))
+        tickets.sort(key=lambda t: int(t["id"]))
         return (tickets, len(tickets))
 
 
 class BugzillaImport(object):
-
     def __init__(self, host):
         log.info(_(u"Bugilla Import plugin initialization"))
         self.host = host
-        host.plugins['TICKETS_IMPORT'].register('bugzilla', self.Import, SHORT_DESC, LONG_DESC)
+        host.plugins["TICKETS_IMPORT"].register(
+            "bugzilla", self.Import, SHORT_DESC, LONG_DESC
+        )
 
     def Import(self, client, location, options=None):
         if not os.path.isabs(location):
-            raise exceptions.DataError(u"An absolute path to XML data need to be given as location")
+            raise exceptions.DataError(
+                u"An absolute path to XML data need to be given as location"
+            )
         bugzilla_parser = BugzillaParser()
         # d = threads.deferToThread(bugzilla_parser.parse, location)
         d = defer.maybeDeferred(bugzilla_parser.parse, location)
--- a/sat/plugins/plugin_tmp_directory_subscription.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_tmp_directory_subscription.py	Wed Jun 27 20:14:46 2018 +0200
@@ -21,6 +21,7 @@
 from sat.core.i18n import _, D_
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 
 
@@ -33,7 +34,7 @@
     C.PI_RECOMMENDATIONS: [],
     C.PI_MAIN: "DirectorySubscription",
     C.PI_HANDLER: "no",
-    C.PI_DESCRIPTION: _("""Implementation of directory subscription""")
+    C.PI_DESCRIPTION: _("""Implementation of directory subscription"""),
 }
 
 
@@ -42,11 +43,15 @@
 
 
 class DirectorySubscription(object):
-
     def __init__(self, host):
         log.info(_("Directory subscription plugin initialization"))
         self.host = host
-        host.importMenu((D_("Service"), D_("Directory subscription")), self.subscribe, security_limit=1, help_string=D_("User directory subscription"))
+        host.importMenu(
+            (D_("Service"), D_("Directory subscription")),
+            self.subscribe,
+            security_limit=1,
+            help_string=D_("User directory subscription"),
+        )
 
     def subscribe(self, raw_data, profile):
         """Request available commands on the jabber search service associated to profile's host.
@@ -59,7 +64,9 @@
 
         def got_services(services):
             service_jid = services[0]
-            session_id, session_data = self.host.plugins["XEP-0050"].requesting.newSession(profile=profile)
+            session_id, session_data = self.host.plugins[
+                "XEP-0050"
+            ].requesting.newSession(profile=profile)
             session_data["jid"] = service_jid
             session_data["node"] = CMD_UPDATE_SUBSCRIBTION
             data = {"session_id": session_id}
--- a/sat/plugins/plugin_xep_0020.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_xep_0020.py	Wed Jun 27 20:14:46 2018 +0200
@@ -20,6 +20,7 @@
 from sat.core.i18n import _
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from sat.core import exceptions
 from twisted.words.xish import domish
@@ -33,7 +34,7 @@
 
 from wokkel import disco, iwokkel, data_form
 
-NS_FEATURE_NEG = 'http://jabber.org/protocol/feature-neg'
+NS_FEATURE_NEG = "http://jabber.org/protocol/feature-neg"
 
 PLUGIN_INFO = {
     C.PI_NAME: "XEP 0020 Plugin",
@@ -42,12 +43,11 @@
     C.PI_PROTOCOLS: ["XEP-0020"],
     C.PI_MAIN: "XEP_0020",
     C.PI_HANDLER: "yes",
-    C.PI_DESCRIPTION: _("""Implementation of Feature Negotiation""")
+    C.PI_DESCRIPTION: _("""Implementation of Feature Negotiation"""),
 }
 
 
 class XEP_0020(object):
-
     def __init__(self, host):
         log.info(_("Plugin XEP_0020 initialization"))
 
@@ -62,7 +62,7 @@
         @raise exceptions.NotFound: no feature element found
         """
         try:
-            feature_elt = elt.elements(NS_FEATURE_NEG, 'feature').next()
+            feature_elt = elt.elements(NS_FEATURE_NEG, "feature").next()
         except StopIteration:
             raise exceptions.NotFound
         return feature_elt
@@ -100,7 +100,11 @@
             values = form.fields[field].values
             result[field] = values[0] if values else None
             if len(values) > 1:
-                log.warning(_(u"More than one value choosed for {}, keeping the first one").format(field))
+                log.warning(
+                    _(
+                        u"More than one value choosed for {}, keeping the first one"
+                    ).format(field)
+                )
         return result
 
     def negotiate(self, feature_elt, name, negotiable_values, namespace):
@@ -126,8 +130,8 @@
         @param options(dict): dict with feature as key and choosed option as value
         @param namespace (None, unicode): form namespace or None to ignore
         """
-        feature_elt = domish.Element((NS_FEATURE_NEG, 'feature'))
-        x_form = data_form.Form('submit', formNamespace=namespace)
+        feature_elt = domish.Element((NS_FEATURE_NEG, "feature"))
+        x_form = data_form.Form("submit", formNamespace=namespace)
         x_form.makeFields(options)
         feature_elt.addChild(x_form.toElement())
         return feature_elt
@@ -138,11 +142,16 @@
         @param options_dict(dict): dict with feature as key and iterable of acceptable options as value
         @param namespace(None, unicode): feature namespace
         """
-        feature_elt = domish.Element((NS_FEATURE_NEG, 'feature'))
-        x_form = data_form.Form('form', formNamespace=namespace)
+        feature_elt = domish.Element((NS_FEATURE_NEG, "feature"))
+        x_form = data_form.Form("form", formNamespace=namespace)
         for field in options_dict:
-            x_form.addField(data_form.Field('list-single', field,
-                            options=[data_form.Option(option) for option in options_dict[field]]))
+            x_form.addField(
+                data_form.Field(
+                    "list-single",
+                    field,
+                    options=[data_form.Option(option) for option in options_dict[field]],
+                )
+            )
         feature_elt.addChild(x_form.toElement())
         return feature_elt
 
@@ -150,8 +159,8 @@
 class XEP_0020_handler(XMPPHandler):
     implements(iwokkel.IDisco)
 
-    def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
+    def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
         return [disco.DiscoFeature(NS_FEATURE_NEG)]
 
-    def getDiscoItems(self, requestor, target, nodeIdentifier=''):
+    def getDiscoItems(self, requestor, target, nodeIdentifier=""):
         return []
--- a/sat/plugins/plugin_xep_0033.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_xep_0033.py	Wed Jun 27 20:14:46 2018 +0200
@@ -20,6 +20,7 @@
 from sat.core.i18n import _
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from sat.core import exceptions
 from wokkel import disco, iwokkel
@@ -27,6 +28,7 @@
 from twisted.words.protocols.jabber.jid import JID
 from twisted.python import failure
 import copy
+
 try:
     from twisted.words.protocols.xmlstream import XMPPHandler
 except ImportError:
@@ -61,7 +63,7 @@
     C.PI_DEPENDENCIES: [],
     C.PI_MAIN: "XEP_0033",
     C.PI_HANDLER: "yes",
-    C.PI_DESCRIPTION: _("""Implementation of Extended Stanza Addressing""")
+    C.PI_DESCRIPTION: _("""Implementation of Extended Stanza Addressing"""),
 }
 
 
@@ -69,38 +71,65 @@
     """
     Implementation for XEP 0033
     """
+
     def __init__(self, host):
         log.info(_("Extended Stanza Addressing plugin initialization"))
         self.host = host
         self.internal_data = {}
-        host.trigger.add("sendMessage", self.sendMessageTrigger, trigger.TriggerManager.MIN_PRIORITY)
+        host.trigger.add(
+            "sendMessage", self.sendMessageTrigger, trigger.TriggerManager.MIN_PRIORITY
+        )
         host.trigger.add("MessageReceived", self.messageReceivedTrigger)
 
-    def sendMessageTrigger(self, client, mess_data, pre_xml_treatments, post_xml_treatments):
+    def sendMessageTrigger(
+        self, client, mess_data, pre_xml_treatments, post_xml_treatments
+    ):
         """Process the XEP-0033 related data to be sent"""
         profile = client.profile
 
         def treatment(mess_data):
-            if not 'address' in mess_data['extra']:
+            if not "address" in mess_data["extra"]:
                 return mess_data
 
             def discoCallback(entities):
                 if not entities:
-                    log.warning(_("XEP-0033 is being used but the server doesn't support it!"))
-                    raise failure.Failure(exceptions.CancelError(u'Cancelled by XEP-0033'))
+                    log.warning(
+                        _("XEP-0033 is being used but the server doesn't support it!")
+                    )
+                    raise failure.Failure(
+                        exceptions.CancelError(u"Cancelled by XEP-0033")
+                    )
                 if mess_data["to"] not in entities:
-                    expected = _(' or ').join([entity.userhost() for entity in entities])
-                    log.warning(_(u"Stanzas using XEP-0033 should be addressed to %(expected)s, not %(current)s!") % {'expected': expected, 'current': mess_data["to"]})
-                    log.warning(_(u"TODO: addressing has been fixed by the backend... fix it in the frontend!"))
+                    expected = _(" or ").join([entity.userhost() for entity in entities])
+                    log.warning(
+                        _(
+                            u"Stanzas using XEP-0033 should be addressed to %(expected)s, not %(current)s!"
+                        )
+                        % {"expected": expected, "current": mess_data["to"]}
+                    )
+                    log.warning(
+                        _(
+                            u"TODO: addressing has been fixed by the backend... fix it in the frontend!"
+                        )
+                    )
                     mess_data["to"] = list(entities)[0].userhostJID()
-                element = mess_data['xml'].addElement('addresses', NS_ADDRESS)
-                entries = [entry.split(':') for entry in mess_data['extra']['address'].split('\n') if entry != '']
+                element = mess_data["xml"].addElement("addresses", NS_ADDRESS)
+                entries = [
+                    entry.split(":")
+                    for entry in mess_data["extra"]["address"].split("\n")
+                    if entry != ""
+                ]
                 for type_, jid_ in entries:
-                    element.addChild(domish.Element((None, 'address'), None, {'type': type_, 'jid': jid_}))
+                    element.addChild(
+                        domish.Element(
+                            (None, "address"), None, {"type": type_, "jid": jid_}
+                        )
+                    )
                 # when the prosody plugin is completed, we can immediately return mess_data from here
                 self.sendAndStoreMessage(mess_data, entries, profile)
                 log.debug("XEP-0033 took over")
-                raise failure.Failure(exceptions.CancelError(u'Cancelled by XEP-0033'))
+                raise failure.Failure(exceptions.CancelError(u"Cancelled by XEP-0033"))
+
             d = self.host.findFeaturesSet(client, [NS_ADDRESS])
             d.addCallbacks(discoCallback, lambda dummy: discoCallback(None))
             return d
@@ -122,6 +151,7 @@
         - change the messageNew signal to eventually pass more than one recipient
         """
         client = self.host.getClient(profile)
+
         def send(mess_data, skip_send=False):
             d = defer.Deferred()
             if not skip_send:
@@ -133,13 +163,13 @@
 
         def discoCallback(entities, to_jid_s):
             history_data = copy.deepcopy(mess_data)
-            history_data['to'] = JID(to_jid_s)
-            history_data['xml']['to'] = to_jid_s
+            history_data["to"] = JID(to_jid_s)
+            history_data["xml"]["to"] = to_jid_s
             if entities:
                 if entities not in self.internal_data[timestamp]:
                     sent_data = copy.deepcopy(mess_data)
-                    sent_data['to'] = JID(JID(to_jid_s).host)
-                    sent_data['xml']['to'] = JID(to_jid_s).host
+                    sent_data["to"] = JID(JID(to_jid_s).host)
+                    sent_data["xml"]["to"] = JID(to_jid_s).host
                     send(sent_data)
                     self.internal_data[timestamp].append(entities)
                 # we still need to fill the history and signal the echo...
@@ -156,8 +186,12 @@
         defer_list = []
         for type_, jid_ in entries:
             d = defer.Deferred()
-            d.addCallback(self.host.findFeaturesSet, client=client, jid_=JID(JID(jid_).host))
-            d.addCallbacks(discoCallback, errback, callbackArgs=[jid_], errbackArgs=[jid_])
+            d.addCallback(
+                self.host.findFeaturesSet, client=client, jid_=JID(JID(jid_).host)
+            )
+            d.addCallbacks(
+                discoCallback, errback, callbackArgs=[jid_], errbackArgs=[jid_]
+            )
             d.callback([NS_ADDRESS])
             defer_list.append(d)
         d = defer.Deferred().addCallback(lambda dummy: self.internal_data.pop(timestamp))
@@ -165,17 +199,21 @@
 
     def messageReceivedTrigger(self, client, message, post_treat):
         """In order to save the addressing information in the history"""
+
         def post_treat_addr(data, addresses):
-            data['extra']['addresses'] = ""
+            data["extra"]["addresses"] = ""
             for address in addresses:
                 # Depending how message has been constructed, we could get here
                 # some noise like "\n        " instead of an address element.
                 if isinstance(address, domish.Element):
-                    data['extra']['addresses'] += '%s:%s\n' % (address['type'], address['jid'])
+                    data["extra"]["addresses"] += "%s:%s\n" % (
+                        address["type"],
+                        address["jid"],
+                    )
             return data
 
         try:
-            addresses = message.elements(NS_ADDRESS, 'addresses').next()
+            addresses = message.elements(NS_ADDRESS, "addresses").next()
         except StopIteration:
             pass  # no addresses
         else:
@@ -194,8 +232,8 @@
         self.host = plugin_parent.host
         self.profile = profile
 
-    def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
+    def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
         return [disco.DiscoFeature(NS_ADDRESS)]
 
-    def getDiscoItems(self, requestor, target, nodeIdentifier=''):
+    def getDiscoItems(self, requestor, target, nodeIdentifier=""):
         return []
--- a/sat/plugins/plugin_xep_0047.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_xep_0047.py	Wed Jun 27 20:14:46 2018 +0200
@@ -19,6 +19,7 @@
 
 from sat.core.i18n import _
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from sat.core.constants import Const as C
 from sat.core import exceptions
@@ -40,15 +41,15 @@
 except ImportError:
     from wokkel.subprotocols import XMPPHandler
 
-MESSAGE = '/message'
+MESSAGE = "/message"
 IQ_SET = '/iq[@type="set"]'
-NS_IBB = 'http://jabber.org/protocol/ibb'
+NS_IBB = "http://jabber.org/protocol/ibb"
 IBB_OPEN = IQ_SET + '/open[@xmlns="' + NS_IBB + '"]'
 IBB_CLOSE = IQ_SET + '/close[@xmlns="' + NS_IBB + '" and @sid="{}"]'
 IBB_IQ_DATA = IQ_SET + '/data[@xmlns="' + NS_IBB + '" and @sid="{}"]'
 IBB_MESSAGE_DATA = MESSAGE + '/data[@xmlns="' + NS_IBB + '" and @sid="{}"]'
 TIMEOUT = 120  # timeout for workflow
-DEFER_KEY = 'finished' # key of the deferred used to track session end
+DEFER_KEY = "finished"  # key of the deferred used to track session end
 
 PLUGIN_INFO = {
     C.PI_NAME: "In-Band Bytestream Plugin",
@@ -58,7 +59,7 @@
     C.PI_PROTOCOLS: ["XEP-0047"],
     C.PI_MAIN: "XEP_0047",
     C.PI_HANDLER: "yes",
-    C.PI_DESCRIPTION: _("""Implementation of In-Band Bytestreams""")
+    C.PI_DESCRIPTION: _("""Implementation of In-Band Bytestreams"""),
 }
 
 
@@ -82,8 +83,11 @@
         @param sid(unicode): session id of client.xep_0047_current_stream
         @param client: %(doc_client)s
         """
-        log.info(u"In-Band Bytestream: TimeOut reached for id {sid} [{profile}]"
-                 .format(sid=sid, profile=client.profile))
+        log.info(
+            u"In-Band Bytestream: TimeOut reached for id {sid} [{profile}]".format(
+                sid=sid, profile=client.profile
+            )
+        )
         self._killSession(sid, client, "TIMEOUT")
 
     def _killSession(self, sid, client, failure_reason=None):
@@ -101,14 +105,14 @@
             return
 
         try:
-            observer_cb = session['observer_cb']
+            observer_cb = session["observer_cb"]
         except KeyError:
             pass
         else:
             client.xmlstream.removeObserver(session["event_data"], observer_cb)
 
-        if session['timer'].active():
-            session['timer'].cancel()
+        if session["timer"].active():
+            session["timer"].cancel()
 
         del client.xep_0047_current_stream[sid]
 
@@ -136,15 +140,15 @@
         @return (dict): session data
         """
         if sid in client.xep_0047_current_stream:
-            raise exceptions.ConflictError(u'A session with this id already exists !')
-        session_data = client.xep_0047_current_stream[sid] = \
-            {'id': sid,
-             DEFER_KEY: defer.Deferred(),
-             'to': to_jid,
-             'stream_object': stream_object,
-             'seq': -1,
-             'timer': reactor.callLater(TIMEOUT, self._timeOut, sid, client),
-            }
+            raise exceptions.ConflictError(u"A session with this id already exists !")
+        session_data = client.xep_0047_current_stream[sid] = {
+            "id": sid,
+            DEFER_KEY: defer.Deferred(),
+            "to": to_jid,
+            "stream_object": stream_object,
+            "seq": -1,
+            "timer": reactor.callLater(TIMEOUT, self._timeOut, sid, client),
+        }
 
         return session_data
 
@@ -155,19 +159,21 @@
         """
         log.debug(_(u"IBB stream opening"))
         iq_elt.handled = True
-        open_elt = iq_elt.elements(NS_IBB, 'open').next()
-        block_size = open_elt.getAttribute('block-size')
-        sid = open_elt.getAttribute('sid')
-        stanza = open_elt.getAttribute('stanza', 'iq')
+        open_elt = iq_elt.elements(NS_IBB, "open").next()
+        block_size = open_elt.getAttribute("block-size")
+        sid = open_elt.getAttribute("sid")
+        stanza = open_elt.getAttribute("stanza", "iq")
         if not sid or not block_size or int(block_size) > 65535:
-            return self._sendError('not-acceptable', sid or None, iq_elt, client)
+            return self._sendError("not-acceptable", sid or None, iq_elt, client)
         if not sid in client.xep_0047_current_stream:
             log.warning(_(u"Ignoring unexpected IBB transfer: %s" % sid))
-            return self._sendError('not-acceptable', sid or None, iq_elt, client)
+            return self._sendError("not-acceptable", sid or None, iq_elt, client)
         session_data = client.xep_0047_current_stream[sid]
-        if session_data["to"] != jid.JID(iq_elt['from']):
-            log.warning(_("sended jid inconsistency (man in the middle attack attempt ?)"))
-            return self._sendError('not-acceptable', sid, iq_elt, client)
+        if session_data["to"] != jid.JID(iq_elt["from"]):
+            log.warning(
+                _("sended jid inconsistency (man in the middle attack attempt ?)")
+            )
+            return self._sendError("not-acceptable", sid, iq_elt, client)
 
         # at this stage, the session looks ok and will be accepted
 
@@ -175,7 +181,9 @@
         session_data["timer"].reset(TIMEOUT)
 
         # we save the xmlstream, events and observer data to allow observer removal
-        session_data["event_data"] = event_data = (IBB_MESSAGE_DATA if stanza == 'message' else IBB_IQ_DATA).format(sid)
+        session_data["event_data"] = event_data = (
+            IBB_MESSAGE_DATA if stanza == "message" else IBB_IQ_DATA
+        ).format(sid)
         session_data["observer_cb"] = observer_cb = self._onIBBData
         event_close = IBB_CLOSE.format(sid)
         # we now set the stream observer to look after data packet
@@ -184,7 +192,7 @@
         client.xmlstream.addObserver(event_data, observer_cb, client=client)
         client.xmlstream.addOnetimeObserver(event_close, self._onIBBClose, client=client)
         # finally, we send the accept stanza
-        iq_result_elt = xmlstream.toResponse(iq_elt, 'result')
+        iq_result_elt = xmlstream.toResponse(iq_elt, "result")
         client.send(iq_result_elt)
 
     def _onIBBClose(self, iq_elt, client):
@@ -194,11 +202,11 @@
         """
         iq_elt.handled = True
         log.debug(_("IBB stream closing"))
-        close_elt = iq_elt.elements(NS_IBB, 'close').next()
+        close_elt = iq_elt.elements(NS_IBB, "close").next()
         # XXX: this observer is only triggered on valid sid, so we don't need to check it
-        sid = close_elt['sid']
+        sid = close_elt["sid"]
 
-        iq_result_elt = xmlstream.toResponse(iq_elt, 'result')
+        iq_result_elt = xmlstream.toResponse(iq_elt, "result")
         client.send(iq_result_elt)
         self._killSession(sid, client)
 
@@ -209,29 +217,33 @@
         @param element(domish.Element): <iq> or <message> stanza
         """
         element.handled = True
-        data_elt = element.elements(NS_IBB, 'data').next()
-        sid = data_elt['sid']
+        data_elt = element.elements(NS_IBB, "data").next()
+        sid = data_elt["sid"]
 
         try:
             session_data = client.xep_0047_current_stream[sid]
         except KeyError:
             log.warning(_(u"Received data for an unknown session id"))
-            return self._sendError('item-not-found', None, element, client)
+            return self._sendError("item-not-found", None, element, client)
 
         from_jid = session_data["to"]
         stream_object = session_data["stream_object"]
 
-        if from_jid.full() != element['from']:
-            log.warning(_(u"sended jid inconsistency (man in the middle attack attempt ?)\ninitial={initial}\ngiven={given}").format(initial=from_jid, given=element['from']))
-            if element.name == 'iq':
-                self._sendError('not-acceptable', sid, element, client)
+        if from_jid.full() != element["from"]:
+            log.warning(
+                _(
+                    u"sended jid inconsistency (man in the middle attack attempt ?)\ninitial={initial}\ngiven={given}"
+                ).format(initial=from_jid, given=element["from"])
+            )
+            if element.name == "iq":
+                self._sendError("not-acceptable", sid, element, client)
             return
 
         session_data["seq"] = (session_data["seq"] + 1) % 65535
         if int(data_elt.getAttribute("seq", -1)) != session_data["seq"]:
             log.warning(_(u"Sequence error"))
-            if element.name == 'iq':
-                reason = 'not-acceptable'
+            if element.name == "iq":
+                reason = "not-acceptable"
                 self._sendError(reason, sid, element, client)
             self.terminateStream(session_data, client, reason)
             return
@@ -245,14 +257,14 @@
         except TypeError:
             # The base64 data is invalid
             log.warning(_(u"Invalid base64 data"))
-            if element.name == 'iq':
-                self._sendError('not-acceptable', sid, element, client)
+            if element.name == "iq":
+                self._sendError("not-acceptable", sid, element, client)
             self.terminateStream(session_data, client, reason)
             return
 
         # we can now ack success
-        if element.name == 'iq':
-            iq_result_elt = xmlstream.toResponse(element, 'result')
+        if element.name == "iq":
+            iq_result_elt = xmlstream.toResponse(element, "result")
             client.send(iq_result_elt)
 
     def _sendError(self, error_condition, sid, iq_elt, client):
@@ -264,7 +276,11 @@
         @param client: %(doc_client)s
         """
         iq_elt = error.StanzaError(error_condition).toResponse(iq_elt)
-        log.warning(u"Error while managing in-band bytestream session, cancelling: {}".format(error_condition))
+        log.warning(
+            u"Error while managing in-band bytestream session, cancelling: {}".format(
+                error_condition
+            )
+        )
         if sid is not None:
             self._killSession(sid, client, error_condition)
         client.send(iq_elt)
@@ -285,11 +301,11 @@
         session_data["block_size"] = block_size
 
         iq_elt = client.IQ()
-        iq_elt['to'] = to_jid.full()
-        open_elt = iq_elt.addElement((NS_IBB, 'open'))
-        open_elt['block-size'] = str(block_size)
-        open_elt['sid'] = sid
-        open_elt['stanza'] = 'iq' # TODO: manage <message> stanza ?
+        iq_elt["to"] = to_jid.full()
+        open_elt = iq_elt.addElement((NS_IBB, "open"))
+        open_elt["block-size"] = str(block_size)
+        open_elt["sid"] = sid
+        open_elt["stanza"] = "iq"  # TODO: manage <message> stanza ?
         args = [session_data, client]
         d = iq_elt.send()
         d.addCallbacks(self._IQDataStreamCb, self._IQDataStreamEb, args, None, args)
@@ -307,11 +323,11 @@
         buffer_ = session_data["stream_object"].read(session_data["block_size"])
         if buffer_:
             next_iq_elt = client.IQ()
-            next_iq_elt['to'] = session_data["to"].full()
-            data_elt = next_iq_elt.addElement((NS_IBB, 'data'))
-            seq = session_data['seq'] = (session_data['seq'] + 1) % 65535
-            data_elt['seq'] = unicode(seq)
-            data_elt['sid'] = session_data['id']
+            next_iq_elt["to"] = session_data["to"].full()
+            data_elt = next_iq_elt.addElement((NS_IBB, "data"))
+            seq = session_data["seq"] = (session_data["seq"] + 1) % 65535
+            data_elt["seq"] = unicode(seq)
+            data_elt["sid"] = session_data["id"]
             data_elt.addContent(base64.b64encode(buffer_))
             args = [session_data, client]
             d = next_iq_elt.send()
@@ -334,11 +350,11 @@
         @param failure_reason(unicode, None): reason of the failure, or None if steam was successful
         """
         iq_elt = client.IQ()
-        iq_elt['to'] = session_data["to"].full()
-        close_elt = iq_elt.addElement((NS_IBB, 'close'))
-        close_elt['sid'] = session_data['id']
+        iq_elt["to"] = session_data["to"].full()
+        close_elt = iq_elt.addElement((NS_IBB, "close"))
+        close_elt["sid"] = session_data["id"]
         iq_elt.send()
-        self._killSession(session_data['id'], client, failure_reason)
+        self._killSession(session_data["id"], client, failure_reason)
 
 
 class XEP_0047_handler(XMPPHandler):
@@ -348,10 +364,12 @@
         self.plugin_parent = parent
 
     def connectionInitialized(self):
-        self.xmlstream.addObserver(IBB_OPEN, self.plugin_parent._onIBBOpen, client=self.parent)
+        self.xmlstream.addObserver(
+            IBB_OPEN, self.plugin_parent._onIBBOpen, client=self.parent
+        )
 
-    def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
+    def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
         return [disco.DiscoFeature(NS_IBB)]
 
-    def getDiscoItems(self, requestor, target, nodeIdentifier=''):
+    def getDiscoItems(self, requestor, target, nodeIdentifier=""):
         return []
--- a/sat/plugins/plugin_xep_0048.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_xep_0048.py	Wed Jun 27 20:14:46 2018 +0200
@@ -23,6 +23,7 @@
 from sat.memory.persistent import PersistentBinaryDict
 from sat.tools import xml_tools
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from twisted.words.xish import domish
 from twisted.words.protocols.jabber import jid
@@ -30,7 +31,7 @@
 
 from twisted.internet import defer
 
-NS_BOOKMARKS = 'storage:bookmarks'
+NS_BOOKMARKS = "storage:bookmarks"
 
 PLUGIN_INFO = {
     C.PI_NAME: "Bookmarks",
@@ -41,28 +42,56 @@
     C.PI_RECOMMENDATIONS: ["XEP-0049"],
     C.PI_MAIN: "XEP_0048",
     C.PI_HANDLER: "no",
-    C.PI_DESCRIPTION: _("""Implementation of bookmarks""")
+    C.PI_DESCRIPTION: _("""Implementation of bookmarks"""),
 }
 
 
 class XEP_0048(object):
-    MUC_TYPE = 'muc'
-    URL_TYPE = 'url'
-    MUC_KEY = 'jid'
-    URL_KEY = 'url'
-    MUC_ATTRS = ('autojoin', 'name')
-    URL_ATTRS = ('name',)
+    MUC_TYPE = "muc"
+    URL_TYPE = "url"
+    MUC_KEY = "jid"
+    URL_KEY = "url"
+    MUC_ATTRS = ("autojoin", "name")
+    URL_ATTRS = ("name",)
 
     def __init__(self, host):
         log.info(_("Bookmarks plugin initialization"))
         self.host = host
         # self.__menu_id = host.registerCallback(self._bookmarksMenu, with_data=True)
         self.__bm_save_id = host.registerCallback(self._bookmarksSaveCb, with_data=True)
-        host.importMenu((D_("Groups"), D_("Bookmarks")), self._bookmarksMenu, security_limit=0, help_string=D_("Use and manage bookmarks"))
-        self.__selected_id = host.registerCallback(self._bookmarkSelectedCb, with_data=True)
-        host.bridge.addMethod("bookmarksList", ".plugin", in_sign='sss', out_sign='a{sa{sa{ss}}}', method=self._bookmarksList, async=True)
-        host.bridge.addMethod("bookmarksRemove", ".plugin", in_sign='ssss', out_sign='', method=self._bookmarksRemove, async=True)
-        host.bridge.addMethod("bookmarksAdd", ".plugin", in_sign='ssa{ss}ss', out_sign='', method=self._bookmarksAdd, async=True)
+        host.importMenu(
+            (D_("Groups"), D_("Bookmarks")),
+            self._bookmarksMenu,
+            security_limit=0,
+            help_string=D_("Use and manage bookmarks"),
+        )
+        self.__selected_id = host.registerCallback(
+            self._bookmarkSelectedCb, with_data=True
+        )
+        host.bridge.addMethod(
+            "bookmarksList",
+            ".plugin",
+            in_sign="sss",
+            out_sign="a{sa{sa{ss}}}",
+            method=self._bookmarksList,
+            async=True,
+        )
+        host.bridge.addMethod(
+            "bookmarksRemove",
+            ".plugin",
+            in_sign="ssss",
+            out_sign="",
+            method=self._bookmarksRemove,
+            async=True,
+        )
+        host.bridge.addMethod(
+            "bookmarksAdd",
+            ".plugin",
+            in_sign="ssa{ss}ss",
+            out_sign="",
+            method=self._bookmarksAdd,
+            async=True,
+        )
         try:
             self.private_plg = self.host.plugins["XEP-0049"]
         except KeyError:
@@ -74,20 +103,22 @@
 
     @defer.inlineCallbacks
     def profileConnected(self, client):
-        local = client.bookmarks_local = PersistentBinaryDict(NS_BOOKMARKS, client.profile)
+        local = client.bookmarks_local = PersistentBinaryDict(
+            NS_BOOKMARKS, client.profile
+        )
         yield local.load()
         if not local:
             local[XEP_0048.MUC_TYPE] = dict()
             local[XEP_0048.URL_TYPE] = dict()
-        private = yield self._getServerBookmarks('private', client.profile)
+        private = yield self._getServerBookmarks("private", client.profile)
         pubsub = client.bookmarks_pubsub = None
 
         for bookmarks in (local, private, pubsub):
             if bookmarks is not None:
                 for (room_jid, data) in bookmarks[XEP_0048.MUC_TYPE].items():
-                    if data.get('autojoin', 'false') == 'true':
-                        nick = data.get('nick', client.jid.user)
-                        self.host.plugins['XEP-0045'].join(client, room_jid, nick, {})
+                    if data.get("autojoin", "false") == "true":
+                        nick = data.get("nick", client.jid.user)
+                        self.host.plugins["XEP-0045"].join(client, room_jid, nick, {})
 
     @defer.inlineCallbacks
     def _getServerBookmarks(self, storage_type, profile):
@@ -101,14 +132,18 @@
         @return: data dictionary, or None if feature is not available
         """
         client = self.host.getClient(profile)
-        if storage_type == 'private':
+        if storage_type == "private":
             try:
-                bookmarks_private_xml = yield self.private_plg.privateXMLGet('storage', NS_BOOKMARKS, profile)
-                data = client.bookmarks_private = self._bookmarkElt2Dict(bookmarks_private_xml)
+                bookmarks_private_xml = yield self.private_plg.privateXMLGet(
+                    "storage", NS_BOOKMARKS, profile
+                )
+                data = client.bookmarks_private = self._bookmarkElt2Dict(
+                    bookmarks_private_xml
+                )
             except (StanzaError, AttributeError):
                 log.info(_("Private XML storage not available"))
                 data = client.bookmarks_private = None
-        elif storage_type == 'pubsub':
+        elif storage_type == "pubsub":
             raise NotImplementedError
         else:
             raise ValueError("storage_type must be 'private' or 'pubsub'")
@@ -124,9 +159,9 @@
         @param bookmarks_elt (domish.Element): bookmarks XML
         @param profile: %(doc_profile)s
         """
-        if storage_type == 'private':
+        if storage_type == "private":
             yield self.private_plg.privateXMLStore(bookmarks_elt, profile)
-        elif storage_type == 'pubsub':
+        elif storage_type == "pubsub":
             raise NotImplementedError
         else:
             raise ValueError("storage_type must be 'private' or 'pubsub'")
@@ -142,12 +177,14 @@
         conf_data = {}
         url_data = {}
 
-        conference_elts = storage_elt.elements(NS_BOOKMARKS, 'conference')
+        conference_elts = storage_elt.elements(NS_BOOKMARKS, "conference")
         for conference_elt in conference_elts:
             try:
                 room_jid = jid.JID(conference_elt[XEP_0048.MUC_KEY])
             except KeyError:
-                log.warning ("invalid bookmark found, igoring it:\n%s" % conference_elt.toXml())
+                log.warning(
+                    "invalid bookmark found, igoring it:\n%s" % conference_elt.toXml()
+                )
                 continue
 
             data = conf_data[room_jid] = {}
@@ -156,17 +193,19 @@
                 if conference_elt.hasAttribute(attr):
                     data[attr] = conference_elt[attr]
             try:
-                data['nick'] = unicode(conference_elt.elements(NS_BOOKMARKS, 'nick').next())
+                data["nick"] = unicode(
+                    conference_elt.elements(NS_BOOKMARKS, "nick").next()
+                )
             except StopIteration:
                 pass
             # TODO: manage password (need to be secured, see XEP-0049 §4)
 
-        url_elts = storage_elt.elements(NS_BOOKMARKS, 'url')
+        url_elts = storage_elt.elements(NS_BOOKMARKS, "url")
         for url_elt in url_elts:
             try:
                 url = url_elt[XEP_0048.URL_KEY]
             except KeyError:
-                log.warning ("invalid bookmark found, igoring it:\n%s" % url_elt.toXml())
+                log.warning("invalid bookmark found, igoring it:\n%s" % url_elt.toXml())
                 continue
             data = url_data[url] = {}
             for attr in XEP_0048.URL_ATTRS:
@@ -185,9 +224,9 @@
         """
         rooms_data = data.get(XEP_0048.MUC_TYPE, {})
         urls_data = data.get(XEP_0048.URL_TYPE, {})
-        storage_elt = domish.Element((NS_BOOKMARKS, 'storage'))
+        storage_elt = domish.Element((NS_BOOKMARKS, "storage"))
         for room_jid in rooms_data:
-            conference_elt = storage_elt.addElement('conference')
+            conference_elt = storage_elt.addElement("conference")
             conference_elt[XEP_0048.MUC_KEY] = room_jid.full()
             for attr in XEP_0048.MUC_ATTRS:
                 try:
@@ -195,12 +234,12 @@
                 except KeyError:
                     pass
             try:
-                conference_elt.addElement('nick', content=rooms_data[room_jid]['nick'])
+                conference_elt.addElement("nick", content=rooms_data[room_jid]["nick"])
             except KeyError:
                 pass
 
         for url in urls_data:
-            url_elt = storage_elt.addElement('url')
+            url_elt = storage_elt.addElement("url")
             url_elt[XEP_0048.URL_KEY] = url
             for attr in XEP_0048.URL_ATTRS:
                 try:
@@ -212,18 +251,20 @@
 
     def _bookmarkSelectedCb(self, data, profile):
         try:
-            room_jid_s, nick = data['index'].split(' ', 1)
+            room_jid_s, nick = data["index"].split(" ", 1)
             room_jid = jid.JID(room_jid_s)
         except (KeyError, RuntimeError):
             log.warning(_("No room jid selected"))
             return {}
 
         client = self.host.getClient(profile)
-        d = self.host.plugins['XEP-0045'].join(client, room_jid, nick, {})
+        d = self.host.plugins["XEP-0045"].join(client, room_jid, nick, {})
+
         def join_eb(failure):
             log.warning(u"Error while trying to join room: {}".format(failure))
             # FIXME: failure are badly managed in plugin XEP-0045. Plugin XEP-0045 need to be fixed before managing errors correctly here
             return {}
+
         d.addCallbacks(lambda dummy: {}, join_eb)
         return d
 
@@ -233,40 +274,54 @@
 
         """
         client = self.host.getClient(profile)
-        xmlui = xml_tools.XMLUI(title=_('Bookmarks manager'))
-        adv_list = xmlui.changeContainer('advanced_list', columns=3, selectable='single', callback_id=self.__selected_id)
-        for bookmarks in (client.bookmarks_local, client.bookmarks_private, client.bookmarks_pubsub):
+        xmlui = xml_tools.XMLUI(title=_("Bookmarks manager"))
+        adv_list = xmlui.changeContainer(
+            "advanced_list",
+            columns=3,
+            selectable="single",
+            callback_id=self.__selected_id,
+        )
+        for bookmarks in (
+            client.bookmarks_local,
+            client.bookmarks_private,
+            client.bookmarks_pubsub,
+        ):
             if bookmarks is None:
                 continue
-            for (room_jid, data) in sorted(bookmarks[XEP_0048.MUC_TYPE].items(), key=lambda item: item[1].get('name',item[0].user)):
+            for (room_jid, data) in sorted(
+                bookmarks[XEP_0048.MUC_TYPE].items(),
+                key=lambda item: item[1].get("name", item[0].user),
+            ):
                 room_jid_s = room_jid.full()
-                adv_list.setRowIndex(u'%s %s' % (room_jid_s, data.get('nick') or client.jid.user))
-                xmlui.addText(data.get('name',''))
+                adv_list.setRowIndex(
+                    u"%s %s" % (room_jid_s, data.get("nick") or client.jid.user)
+                )
+                xmlui.addText(data.get("name", ""))
                 xmlui.addJid(room_jid)
-                if data.get('autojoin', 'false') == 'true':
-                    xmlui.addText('autojoin')
+                if data.get("autojoin", "false") == "true":
+                    xmlui.addText("autojoin")
                 else:
                     xmlui.addEmpty()
         adv_list.end()
-        xmlui.addDivider('dash')
+        xmlui.addDivider("dash")
         xmlui.addText(_("add a bookmark"))
         xmlui.changeContainer("pairs")
-        xmlui.addLabel(_('Name'))
-        xmlui.addString('name')
-        xmlui.addLabel(_('jid'))
-        xmlui.addString('jid')
-        xmlui.addLabel(_('Nickname'))
-        xmlui.addString('nick', client.jid.user)
-        xmlui.addLabel(_('Autojoin'))
-        xmlui.addBool('autojoin')
+        xmlui.addLabel(_("Name"))
+        xmlui.addString("name")
+        xmlui.addLabel(_("jid"))
+        xmlui.addString("jid")
+        xmlui.addLabel(_("Nickname"))
+        xmlui.addString("nick", client.jid.user)
+        xmlui.addLabel(_("Autojoin"))
+        xmlui.addBool("autojoin")
         xmlui.changeContainer("vertical")
-        xmlui.addButton(self.__bm_save_id, _("Save"), ('name', 'jid', 'nick', 'autojoin'))
-        return {'xmlui': xmlui.toXml()}
+        xmlui.addButton(self.__bm_save_id, _("Save"), ("name", "jid", "nick", "autojoin"))
+        return {"xmlui": xmlui.toXml()}
 
     def _bookmarksSaveCb(self, data, profile):
         bm_data = xml_tools.XMLUIResult2DataFormResult(data)
         try:
-            location = jid.JID(bm_data.pop('jid'))
+            location = jid.JID(bm_data.pop("jid"))
         except KeyError:
             raise exceptions.InternalError("Can't find mandatory key")
         d = self.addBookmark(XEP_0048.MUC_TYPE, location, bm_data, profile_key=profile)
@@ -274,7 +329,9 @@
         return d
 
     @defer.inlineCallbacks
-    def addBookmark(self, type_, location, data, storage_type="auto", profile_key=C.PROF_KEY_NONE):
+    def addBookmark(
+        self, type_, location, data, storage_type="auto", profile_key=C.PROF_KEY_NONE
+    ):
         """Store a new bookmark
 
         @param type_: bookmark type, one of:
@@ -293,21 +350,21 @@
             - "local": Store in SàT database
         @param profile_key: %(doc_profile_key)s
         """
-        assert storage_type in ('auto', 'pubsub', 'private', 'local')
-        if type_ == XEP_0048.URL_TYPE and {'autojoin', 'nick'}.intersection(data.keys()):
-                raise ValueError("autojoin or nick can't be used with URLs")
+        assert storage_type in ("auto", "pubsub", "private", "local")
+        if type_ == XEP_0048.URL_TYPE and {"autojoin", "nick"}.intersection(data.keys()):
+            raise ValueError("autojoin or nick can't be used with URLs")
         client = self.host.getClient(profile_key)
-        if storage_type == 'auto':
+        if storage_type == "auto":
             if client.bookmarks_pubsub is not None:
-                storage_type = 'pubsub'
+                storage_type = "pubsub"
             elif client.bookmarks_private is not None:
-                storage_type = 'private'
+                storage_type = "private"
             else:
-                storage_type = 'local'
+                storage_type = "local"
                 log.warning(_("Bookmarks will be local only"))
             log.info(_('Type selected for "auto" storage: %s') % storage_type)
 
-        if storage_type == 'local':
+        if storage_type == "local":
             client.bookmarks_local[type_][location] = data
             yield client.bookmarks_local.force(type_)
         else:
@@ -317,7 +374,9 @@
             yield self._setServerBookmarks(storage_type, bookmark_elt, client.profile)
 
     @defer.inlineCallbacks
-    def removeBookmark(self, type_, location, storage_type="all", profile_key=C.PROF_KEY_NONE):
+    def removeBookmark(
+        self, type_, location, storage_type="all", profile_key=C.PROF_KEY_NONE
+    ):
         """Remove a stored bookmark
 
         @param type_: bookmark type, one of:
@@ -331,26 +390,26 @@
             - "local": Store in SàT database
         @param profile_key: %(doc_profile_key)s
         """
-        assert storage_type in ('all', 'pubsub', 'private', 'local')
+        assert storage_type in ("all", "pubsub", "private", "local")
         client = self.host.getClient(profile_key)
 
-        if storage_type in ('all', 'local'):
+        if storage_type in ("all", "local"):
             try:
                 del client.bookmarks_local[type_][location]
                 yield client.bookmarks_local.force(type_)
             except KeyError:
                 log.debug("Bookmark is not present in local storage")
 
-        if storage_type in ('all', 'private'):
-            bookmarks = yield self._getServerBookmarks('private', client.profile)
+        if storage_type in ("all", "private"):
+            bookmarks = yield self._getServerBookmarks("private", client.profile)
             try:
                 del bookmarks[type_][location]
                 bookmark_elt = self._dict2BookmarkElt(type_, bookmarks)
-                yield self._setServerBookmarks('private', bookmark_elt, client.profile)
+                yield self._setServerBookmarks("private", bookmark_elt, client.profile)
             except KeyError:
                 log.debug("Bookmark is not present in private storage")
 
-        if storage_type == 'pubsub':
+        if storage_type == "pubsub":
             raise NotImplementedError
 
     def _bookmarksList(self, type_, storage_location, profile_key=C.PROF_KEY_NONE):
@@ -381,10 +440,10 @@
                 ret[_storage_location][bookmark.full()] = data[bookmark].copy()
             return ret
 
-        for _storage_location in ('local', 'private', 'pubsub'):
-            if storage_location in ('all', _storage_location):
+        for _storage_location in ("local", "private", "pubsub"):
+            if storage_location in ("all", _storage_location):
                 ret[_storage_location] = {}
-                if _storage_location in ('private',):
+                if _storage_location in ("private",):
                     # we update distant bookmarks, just in case an other client added something
                     d = self._getServerBookmarks(_storage_location, client.profile)
                 else:
@@ -394,7 +453,9 @@
 
         return ret_d
 
-    def _bookmarksRemove(self, type_, location, storage_location, profile_key=C.PROF_KEY_NONE):
+    def _bookmarksRemove(
+        self, type_, location, storage_location, profile_key=C.PROF_KEY_NONE
+    ):
         """Return stored bookmarks
 
         @param type_: bookmark type, one of:
@@ -412,7 +473,9 @@
             location = jid.JID(location)
         return self.removeBookmark(type_, location, storage_location, profile_key)
 
-    def _bookmarksAdd(self, type_, location, data, storage_type="auto", profile_key=C.PROF_KEY_NONE):
+    def _bookmarksAdd(
+        self, type_, location, data, storage_type="auto", profile_key=C.PROF_KEY_NONE
+    ):
         if type_ == XEP_0048.MUC_TYPE:
             location = jid.JID(location)
         return self.addBookmark(type_, location, data, storage_type, profile_key)
@@ -427,21 +490,26 @@
         txt_cmd = self.host.plugins[C.TEXT_CMDS]
 
         options = mess_data["unparsed"].strip().split()
-        if options and options[0] not in ('autojoin', 'remove'):
+        if options and options[0] not in ("autojoin", "remove"):
             txt_cmd.feedBack(client, _("Bad arguments"), mess_data)
             return False
 
         room_jid = mess_data["to"].userhostJID()
 
         if "remove" in options:
-            self.removeBookmark(XEP_0048.MUC_TYPE, room_jid, profile_key = client.profile)
-            txt_cmd.feedBack(client, _("All [%s] bookmarks are being removed") % room_jid.full(), mess_data)
+            self.removeBookmark(XEP_0048.MUC_TYPE, room_jid, profile_key=client.profile)
+            txt_cmd.feedBack(
+                client,
+                _("All [%s] bookmarks are being removed") % room_jid.full(),
+                mess_data,
+            )
             return False
 
-        data = { "name": room_jid.user,
-                 "nick": client.jid.user,
-                 "autojoin": "true" if "autojoin" in options else "false",
-               }
+        data = {
+            "name": room_jid.user,
+            "nick": client.jid.user,
+            "autojoin": "true" if "autojoin" in options else "false",
+        }
         self.addBookmark(XEP_0048.MUC_TYPE, room_jid, data, profile_key=client.profile)
         txt_cmd.feedBack(client, _("Bookmark added"), mess_data)
 
--- a/sat/plugins/plugin_xep_0049.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_xep_0049.py	Wed Jun 27 20:14:46 2018 +0200
@@ -20,12 +20,12 @@
 from sat.core.i18n import _
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from wokkel import compat
 from twisted.words.xish import domish
 
 
-
 PLUGIN_INFO = {
     C.PI_NAME: "XEP-0049 Plugin",
     C.PI_IMPORT_NAME: "XEP-0049",
@@ -34,12 +34,12 @@
     C.PI_DEPENDENCIES: [],
     C.PI_MAIN: "XEP_0049",
     C.PI_HANDLER: "no",
-    C.PI_DESCRIPTION: _("""Implementation of private XML storage""")
+    C.PI_DESCRIPTION: _("""Implementation of private XML storage"""),
 }
 
 
 class XEP_0049(object):
-    NS_PRIVATE = 'jabber:iq:private'
+    NS_PRIVATE = "jabber:iq:private"
 
     def __init__(self, host):
         log.info(_("Plugin XEP-0049 initialization"))
@@ -55,7 +55,7 @@
         client = self.host.getClient(profile_key)
         # XXX: feature announcement in disco#info is not mandatory in XEP-0049, so we have to try to use private XML, and react according to the answer
         iq_elt = compat.IQ(client.xmlstream)
-        query_elt = iq_elt.addElement('query', XEP_0049.NS_PRIVATE)
+        query_elt = iq_elt.addElement("query", XEP_0049.NS_PRIVATE)
         query_elt.addChild(element)
         return iq_elt.send()
 
@@ -69,13 +69,14 @@
         """
         client = self.host.getClient(profile_key)
         # XXX: see privateXMLStore note about feature checking
-        iq_elt = compat.IQ(client.xmlstream, 'get')
-        query_elt = iq_elt.addElement('query', XEP_0049.NS_PRIVATE)
+        iq_elt = compat.IQ(client.xmlstream, "get")
+        query_elt = iq_elt.addElement("query", XEP_0049.NS_PRIVATE)
         query_elt.addElement(node_name, namespace)
+
         def getCb(answer_iq_elt):
-            answer_query_elt = answer_iq_elt.elements(XEP_0049.NS_PRIVATE, 'query').next()
+            answer_query_elt = answer_iq_elt.elements(XEP_0049.NS_PRIVATE, "query").next()
             return answer_query_elt.firstChildElement()
+
         d = iq_elt.send()
         d.addCallback(getCb)
         return d
-
--- a/sat/plugins/plugin_xep_0050.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_xep_0050.py	Wed Jun 27 20:14:46 2018 +0200
@@ -20,6 +20,7 @@
 from sat.core.i18n import _, D_
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from twisted.words.protocols.jabber import jid
 from twisted.words.protocols import jabber
@@ -41,7 +42,7 @@
 from collections import namedtuple
 
 try:
-    from collections import OrderedDict # only available from python 2.7
+    from collections import OrderedDict  # only available from python 2.7
 except ImportError:
     from ordereddict import OrderedDict
 
@@ -51,12 +52,16 @@
 ID_CMD_NODE = disco.DiscoIdentity("automation", "command-node")
 CMD_REQUEST = IQ_SET + '/command[@xmlns="' + NS_COMMANDS + '"]'
 
-SHOWS = OrderedDict([('default', _('Online')),
-                     ('away', _('Away')),
-                     ('chat', _('Free for chat')),
-                     ('dnd', _('Do not disturb')),
-                     ('xa', _('Left')),
-                     ('disconnect', _('Disconnect'))])
+SHOWS = OrderedDict(
+    [
+        ("default", _("Online")),
+        ("away", _("Away")),
+        ("chat", _("Free for chat")),
+        ("dnd", _("Do not disturb")),
+        ("xa", _("Left")),
+        ("disconnect", _("Disconnect")),
+    ]
+)
 
 PLUGIN_INFO = {
     C.PI_NAME: "Ad-Hoc Commands",
@@ -65,12 +70,11 @@
     C.PI_PROTOCOLS: ["XEP-0050"],
     C.PI_MAIN: "XEP_0050",
     C.PI_HANDLER: "yes",
-    C.PI_DESCRIPTION: _("""Implementation of Ad-Hoc Commands""")
+    C.PI_DESCRIPTION: _("""Implementation of Ad-Hoc Commands"""),
 }
 
 
 class AdHocError(Exception):
-
     def __init__(self, error_const):
         """ Error to be used from callback
         @param error_const: one of XEP_0050.ERROR
@@ -78,16 +82,37 @@
         assert error_const in XEP_0050.ERROR
         self.callback_error = error_const
 
+
 class AdHocCommand(XMPPHandler):
     implements(iwokkel.IDisco)
 
-    def  __init__(self, parent, callback, label, node, features, timeout, allowed_jids, allowed_groups, allowed_magics, forbidden_jids, forbidden_groups, client):
+    def __init__(
+        self,
+        parent,
+        callback,
+        label,
+        node,
+        features,
+        timeout,
+        allowed_jids,
+        allowed_groups,
+        allowed_magics,
+        forbidden_jids,
+        forbidden_groups,
+        client,
+    ):
         self.parent = parent
         self.callback = callback
         self.label = label
         self.node = node
         self.features = [disco.DiscoFeature(feature) for feature in features]
-        self.allowed_jids, self.allowed_groups, self.allowed_magics, self.forbidden_jids, self.forbidden_groups = allowed_jids, allowed_groups, allowed_magics, forbidden_jids, forbidden_groups
+        self.allowed_jids, self.allowed_groups, self.allowed_magics, self.forbidden_jids, self.forbidden_groups = (
+            allowed_jids,
+            allowed_groups,
+            allowed_magics,
+            forbidden_jids,
+            forbidden_groups,
+        )
         self.client = client
         self.sessions = Sessions(timeout=timeout)
 
@@ -95,7 +120,7 @@
         return self.label
 
     def isAuthorised(self, requestor):
-        if '@ALL@' in self.allowed_magics:
+        if "@ALL@" in self.allowed_magics:
             return True
         forbidden = set(self.forbidden_jids)
         for group in self.forbidden_groups:
@@ -107,18 +132,25 @@
             try:
                 allowed.update(self.client.roster.getJidsFromGroup(group))
             except exceptions.UnknownGroupError:
-                log.warning(_(u"The groups [%(group)s] is unknown for profile [%(profile)s])" % {'group':group, 'profile':self.client.profile}))
+                log.warning(
+                    _(
+                        u"The groups [%(group)s] is unknown for profile [%(profile)s])"
+                        % {"group": group, "profile": self.client.profile}
+                    )
+                )
         if requestor.userhostJID() in allowed:
             return True
         return False
 
-    def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
-        if nodeIdentifier != NS_COMMANDS: # FIXME: we should manage other disco nodes here
+    def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
+        if (
+            nodeIdentifier != NS_COMMANDS
+        ):  # FIXME: we should manage other disco nodes here
             return []
         # identities = [ID_CMD_LIST if self.node == NS_COMMANDS else ID_CMD_NODE] # FIXME
         return [disco.DiscoFeature(NS_COMMANDS)] + self.features
 
-    def getDiscoItems(self, requestor, target, nodeIdentifier=''):
+    def getDiscoItems(self, requestor, target, nodeIdentifier=""):
         return []
 
     def _sendAnswer(self, callback_data, session_id, request):
@@ -134,30 +166,30 @@
         @return: deferred
         """
         payload, status, actions, note = callback_data
-        assert(isinstance(payload, domish.Element) or payload is None)
-        assert(status in XEP_0050.STATUS)
+        assert isinstance(payload, domish.Element) or payload is None
+        assert status in XEP_0050.STATUS
         if not actions:
             actions = [XEP_0050.ACTION.EXECUTE]
-        result = domish.Element((None, 'iq'))
-        result['type'] = 'result'
-        result['id'] = request['id']
-        result['to'] = request['from']
-        command_elt = result.addElement('command', NS_COMMANDS)
-        command_elt['sessionid'] = session_id
-        command_elt['node'] = self.node
-        command_elt['status'] = status
+        result = domish.Element((None, "iq"))
+        result["type"] = "result"
+        result["id"] = request["id"]
+        result["to"] = request["from"]
+        command_elt = result.addElement("command", NS_COMMANDS)
+        command_elt["sessionid"] = session_id
+        command_elt["node"] = self.node
+        command_elt["status"] = status
 
         if status != XEP_0050.STATUS.CANCELED:
             if status != XEP_0050.STATUS.COMPLETED:
-                actions_elt = command_elt.addElement('actions')
-                actions_elt['execute'] = actions[0]
+                actions_elt = command_elt.addElement("actions")
+                actions_elt["execute"] = actions[0]
                 for action in actions:
                     actions_elt.addElement(action)
 
             if note is not None:
                 note_type, note_mess = note
-                note_elt = command_elt.addElement('note', content=note_mess)
-                note_elt['type'] = note_type
+                note_elt = command_elt.addElement("note", content=note_mess)
+                note_elt["type"] = note_type
 
             if payload is not None:
                 command_elt.addChild(payload)
@@ -181,54 +213,116 @@
 
     def onRequest(self, command_elt, requestor, action, session_id):
         if not self.isAuthorised(requestor):
-            return self._sendError(XEP_0050.ERROR.FORBIDDEN, session_id, command_elt.parent)
+            return self._sendError(
+                XEP_0050.ERROR.FORBIDDEN, session_id, command_elt.parent
+            )
         if session_id:
             try:
                 session_data = self.sessions[session_id]
             except KeyError:
-                return self._sendError(XEP_0050.ERROR.SESSION_EXPIRED, session_id, command_elt.parent)
-            if session_data['requestor'] != requestor:
-                return self._sendError(XEP_0050.ERROR.FORBIDDEN, session_id, command_elt.parent)
+                return self._sendError(
+                    XEP_0050.ERROR.SESSION_EXPIRED, session_id, command_elt.parent
+                )
+            if session_data["requestor"] != requestor:
+                return self._sendError(
+                    XEP_0050.ERROR.FORBIDDEN, session_id, command_elt.parent
+                )
         else:
             session_id, session_data = self.sessions.newSession()
-            session_data['requestor'] = requestor
+            session_data["requestor"] = requestor
         if action == XEP_0050.ACTION.CANCEL:
             d = defer.succeed((None, XEP_0050.STATUS.CANCELED, None, None))
         else:
-            d = defer.maybeDeferred(self.callback, command_elt, session_data, action, self.node, self.client.profile)
+            d = defer.maybeDeferred(
+                self.callback,
+                command_elt,
+                session_data,
+                action,
+                self.node,
+                self.client.profile,
+            )
         d.addCallback(self._sendAnswer, session_id, command_elt.parent)
-        d.addErrback(lambda failure, request: self._sendError(failure.value.callback_error, session_id, request), command_elt.parent)
+        d.addErrback(
+            lambda failure, request: self._sendError(
+                failure.value.callback_error, session_id, request
+            ),
+            command_elt.parent,
+        )
 
 
 class XEP_0050(object):
-    STATUS = namedtuple('Status', ('EXECUTING', 'COMPLETED', 'CANCELED'))('executing', 'completed', 'canceled')
-    ACTION = namedtuple('Action', ('EXECUTE', 'CANCEL', 'NEXT', 'PREV'))('execute', 'cancel', 'next', 'prev')
-    NOTE = namedtuple('Note', ('INFO','WARN','ERROR'))('info','warn','error')
-    ERROR = namedtuple('Error', ('MALFORMED_ACTION', 'BAD_ACTION', 'BAD_LOCALE', 'BAD_PAYLOAD', 'BAD_SESSIONID', 'SESSION_EXPIRED',
-                                 'FORBIDDEN', 'ITEM_NOT_FOUND', 'FEATURE_NOT_IMPLEMENTED', 'INTERNAL'))(('bad-request', 'malformed-action'),
-                                 ('bad-request', 'bad-action'), ('bad-request', 'bad-locale'), ('bad-request','bad-payload'),
-                                 ('bad-request','bad-sessionid'), ('not-allowed','session-expired'), ('forbidden', None),
-                                 ('item-not-found', None), ('feature-not-implemented', None), ('internal-server-error', None)) # XEP-0050 §4.4 Table 5
+    STATUS = namedtuple("Status", ("EXECUTING", "COMPLETED", "CANCELED"))(
+        "executing", "completed", "canceled"
+    )
+    ACTION = namedtuple("Action", ("EXECUTE", "CANCEL", "NEXT", "PREV"))(
+        "execute", "cancel", "next", "prev"
+    )
+    NOTE = namedtuple("Note", ("INFO", "WARN", "ERROR"))("info", "warn", "error")
+    ERROR = namedtuple(
+        "Error",
+        (
+            "MALFORMED_ACTION",
+            "BAD_ACTION",
+            "BAD_LOCALE",
+            "BAD_PAYLOAD",
+            "BAD_SESSIONID",
+            "SESSION_EXPIRED",
+            "FORBIDDEN",
+            "ITEM_NOT_FOUND",
+            "FEATURE_NOT_IMPLEMENTED",
+            "INTERNAL",
+        ),
+    )(
+        ("bad-request", "malformed-action"),
+        ("bad-request", "bad-action"),
+        ("bad-request", "bad-locale"),
+        ("bad-request", "bad-payload"),
+        ("bad-request", "bad-sessionid"),
+        ("not-allowed", "session-expired"),
+        ("forbidden", None),
+        ("item-not-found", None),
+        ("feature-not-implemented", None),
+        ("internal-server-error", None),
+    )  # XEP-0050 §4.4 Table 5
 
     def __init__(self, host):
         log.info(_("plugin XEP-0050 initialization"))
         self.host = host
         self.requesting = Sessions()
         self.answering = {}
-        host.bridge.addMethod("adHocRun", ".plugin", in_sign='sss', out_sign='s',
-                              method=self._run,
-                              async=True)
-        host.bridge.addMethod("adHocList", ".plugin", in_sign='ss', out_sign='s',
-                              method=self._list,
-                              async=True)
-        self.__requesting_id = host.registerCallback(self._requestingEntity, with_data=True)
-        host.importMenu((D_("Service"), D_("Commands")), self._commandsMenu, security_limit=2, help_string=D_("Execute ad-hoc commands"))
+        host.bridge.addMethod(
+            "adHocRun",
+            ".plugin",
+            in_sign="sss",
+            out_sign="s",
+            method=self._run,
+            async=True,
+        )
+        host.bridge.addMethod(
+            "adHocList",
+            ".plugin",
+            in_sign="ss",
+            out_sign="s",
+            method=self._list,
+            async=True,
+        )
+        self.__requesting_id = host.registerCallback(
+            self._requestingEntity, with_data=True
+        )
+        host.importMenu(
+            (D_("Service"), D_("Commands")),
+            self._commandsMenu,
+            security_limit=2,
+            help_string=D_("Execute ad-hoc commands"),
+        )
 
     def getHandler(self, client):
         return XEP_0050_handler(self)
 
     def profileConnected(self, client):
-        self.addAdHocCommand(self._statusCallback, _("Status"), profile_key=client.profile)
+        self.addAdHocCommand(
+            self._statusCallback, _("Status"), profile_key=client.profile
+        )
 
     def profileDisconnected(self, client):
         try:
@@ -242,7 +336,7 @@
         form_ui = xml_tools.XMLUI("form", submit_id=self.__requesting_id)
 
         if not no_instructions:
-            form_ui.addText(_("Please select a command"), 'instructions')
+            form_ui.addText(_("Please select a command"), "instructions")
 
         options = [(item.nodeIdentifier, item.name) for item in items]
         form_ui.addList("node", options)
@@ -254,12 +348,12 @@
         @param type_: note type (see XEP-0050 §4.3)
         @return: a C.XMLUI_DATA_LVL_* constant
         """
-        if type_ == 'error':
+        if type_ == "error":
             return C.XMLUI_DATA_LVL_ERROR
-        elif type_ == 'warn':
+        elif type_ == "warn":
             return C.XMLUI_DATA_LVL_WARNING
         else:
-            if type_ != 'info':
+            if type_ != "info":
                 log.warning(_(u"Invalid note type [%s], using info") % type_)
             return C.XMLUI_DATA_LVL_INFO
 
@@ -269,10 +363,11 @@
         @param notes (list): list of tuple (level, message)
         @return: list of messages
         """
-        lvl_map = {C.XMLUI_DATA_LVL_INFO: '',
-                   C.XMLUI_DATA_LVL_WARNING: "%s: " % _("WARNING"),
-                   C.XMLUI_DATA_LVL_ERROR: "%s: " % _("ERROR")
-                  }
+        lvl_map = {
+            C.XMLUI_DATA_LVL_INFO: "",
+            C.XMLUI_DATA_LVL_WARNING: "%s: " % _("WARNING"),
+            C.XMLUI_DATA_LVL_ERROR: "%s: " % _("ERROR"),
+        }
         return [u"%s%s" % (lvl_map[lvl], msg) for lvl, msg in notes]
 
     def _commandsAnswer2XMLUI(self, iq_elt, session_id, session_data):
@@ -284,7 +379,7 @@
 
         """
         command_elt = iq_elt.elements(NS_COMMANDS, "command").next()
-        status = command_elt.getAttribute('status', XEP_0050.STATUS.EXECUTING)
+        status = command_elt.getAttribute("status", XEP_0050.STATUS.EXECUTING)
         if status in [XEP_0050.STATUS.COMPLETED, XEP_0050.STATUS.CANCELED]:
             # the command session is finished, we purge our session
             del self.requesting[session_id]
@@ -292,27 +387,35 @@
                 session_id = None
             else:
                 return None
-        remote_session_id = command_elt.getAttribute('sessionid')
+        remote_session_id = command_elt.getAttribute("sessionid")
         if remote_session_id:
-            session_data['remote_id'] = remote_session_id
+            session_data["remote_id"] = remote_session_id
         notes = []
-        for note_elt in command_elt.elements(NS_COMMANDS, 'note'):
-            notes.append((self._getDataLvl(note_elt.getAttribute('type', 'info')),
-                          unicode(note_elt)))
-        for data_elt in command_elt.elements(data_form.NS_X_DATA, 'x'):
-            if data_elt['type'] in ('form', 'result'):
+        for note_elt in command_elt.elements(NS_COMMANDS, "note"):
+            notes.append(
+                (
+                    self._getDataLvl(note_elt.getAttribute("type", "info")),
+                    unicode(note_elt),
+                )
+            )
+        for data_elt in command_elt.elements(data_form.NS_X_DATA, "x"):
+            if data_elt["type"] in ("form", "result"):
                 break
         else:
             # no matching data element found
             if status != XEP_0050.STATUS.COMPLETED:
-                log.warning(_("No known payload found in ad-hoc command result, aborting"))
+                log.warning(
+                    _("No known payload found in ad-hoc command result, aborting")
+                )
                 del self.requesting[session_id]
-                return xml_tools.XMLUI(C.XMLUI_DIALOG,
-                                       dialog_opt = {C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_NOTE,
-                                                     C.XMLUI_DATA_MESS: _("No payload found"),
-                                                     C.XMLUI_DATA_LVL: C.XMLUI_DATA_LVL_ERROR,
-                                                    }
-                                      )
+                return xml_tools.XMLUI(
+                    C.XMLUI_DIALOG,
+                    dialog_opt={
+                        C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_NOTE,
+                        C.XMLUI_DATA_MESS: _("No payload found"),
+                        C.XMLUI_DATA_LVL: C.XMLUI_DATA_LVL_ERROR,
+                    },
+                )
             if not notes:
                 # the status is completed, and we have no note to show
                 return None
@@ -321,13 +424,14 @@
             # if we have more, we show a dialog with "info" level, and all notes merged
             dlg_level = notes[0][0] if len(notes) == 1 else C.XMLUI_DATA_LVL_INFO
             return xml_tools.XMLUI(
-                                   C.XMLUI_DIALOG,
-                                   dialog_opt = {C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_NOTE,
-                                                 C.XMLUI_DATA_MESS: u'\n'.join(self._mergeNotes(notes)),
-                                                 C.XMLUI_DATA_LVL: dlg_level,
-                                                },
-                                   session_id = session_id
-                                  )
+                C.XMLUI_DIALOG,
+                dialog_opt={
+                    C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_NOTE,
+                    C.XMLUI_DATA_MESS: u"\n".join(self._mergeNotes(notes)),
+                    C.XMLUI_DATA_LVL: dlg_level,
+                },
+                session_id=session_id,
+            )
 
         if session_id is None:
             return xml_tools.dataFormEltResult2XMLUI(data_elt)
@@ -338,8 +442,8 @@
 
     def _requestingEntity(self, data, profile):
         def serialise(ret_data):
-            if 'xmlui' in ret_data:
-                ret_data['xmlui'] = ret_data['xmlui'].toXml()
+            if "xmlui" in ret_data:
+                ret_data["xmlui"] = ret_data["xmlui"].toXml()
             return ret_data
 
         d = self.requestingEntity(data, profile)
@@ -354,7 +458,7 @@
         @return: callback dict result (with "xmlui" corresponding to the answering dialog, or empty if it's finished without error)
 
         """
-        if C.bool(data.get('cancelled', C.BOOL_FALSE)):
+        if C.bool(data.get("cancelled", C.BOOL_FALSE)):
             return defer.succeed({})
         client = self.host.getClient(profile)
         # TODO: cancel, prev and next are not managed
@@ -363,49 +467,53 @@
         if "session_id" not in data:
             # we just had the jid, we now request it for the available commands
             session_id, session_data = self.requesting.newSession(profile=client.profile)
-            entity = jid.JID(data[xml_tools.SAT_FORM_PREFIX+'jid'])
-            session_data['jid'] = entity
+            entity = jid.JID(data[xml_tools.SAT_FORM_PREFIX + "jid"])
+            session_data["jid"] = entity
             d = self.list(client, entity)
 
             def sendItems(xmlui):
-                xmlui.session_id = session_id # we need to keep track of the session
-                return {'xmlui': xmlui}
+                xmlui.session_id = session_id  # we need to keep track of the session
+                return {"xmlui": xmlui}
 
             d.addCallback(sendItems)
         else:
             # we have started a several forms sessions
             try:
-                session_data = self.requesting.profileGet(data["session_id"], client.profile)
+                session_data = self.requesting.profileGet(
+                    data["session_id"], client.profile
+                )
             except KeyError:
-                log.warning ("session id doesn't exist, session has probably expired")
+                log.warning("session id doesn't exist, session has probably expired")
                 # TODO: send error dialog
                 return defer.succeed({})
             session_id = data["session_id"]
-            entity = session_data['jid']
+            entity = session_data["jid"]
             try:
-                session_data['node']
+                session_data["node"]
                 # node has already been received
             except KeyError:
                 # it's the first time we know the node, we save it in session data
-                session_data['node'] = data[xml_tools.SAT_FORM_PREFIX+'node']
+                session_data["node"] = data[xml_tools.SAT_FORM_PREFIX + "node"]
 
             # we request execute node's command
-            iq_elt = compat.IQ(client.xmlstream, 'set')
-            iq_elt['to'] = entity.full()
+            iq_elt = compat.IQ(client.xmlstream, "set")
+            iq_elt["to"] = entity.full()
             command_elt = iq_elt.addElement("command", NS_COMMANDS)
-            command_elt['node'] = session_data['node']
-            command_elt['action'] = XEP_0050.ACTION.EXECUTE
+            command_elt["node"] = session_data["node"]
+            command_elt["action"] = XEP_0050.ACTION.EXECUTE
             try:
                 # remote_id is the XEP_0050 sessionid used by answering command
                 # while session_id is our own session id used with the frontend
-                command_elt['sessionid'] = session_data['remote_id']
+                command_elt["sessionid"] = session_data["remote_id"]
             except KeyError:
                 pass
 
-            command_elt.addChild(xml_tools.XMLUIResultToElt(data)) # We add the XMLUI result to the command payload
+            command_elt.addChild(
+                xml_tools.XMLUIResultToElt(data)
+            )  # We add the XMLUI result to the command payload
             d = iq_elt.send()
             d.addCallback(self._commandsAnswer2XMLUI, session_id, session_data)
-            d.addCallback(lambda xmlui: {'xmlui': xmlui} if xmlui is not None else {})
+            d.addCallback(lambda xmlui: {"xmlui": xmlui} if xmlui is not None else {})
 
         return d
 
@@ -415,23 +523,27 @@
 
         """
         form_ui = xml_tools.XMLUI("form", submit_id=self.__requesting_id)
-        form_ui.addText(_("Please enter target jid"), 'instructions')
+        form_ui.addText(_("Please enter target jid"), "instructions")
         form_ui.changeContainer("pairs")
         form_ui.addLabel("jid")
         form_ui.addString("jid", value=self.host.getClient(profile).jid.host)
-        return {'xmlui': form_ui.toXml()}
+        return {"xmlui": form_ui.toXml()}
 
     def _statusCallback(self, command_elt, session_data, action, node, profile):
         """ Ad-hoc command used to change the "show" part of status """
-        actions = session_data.setdefault('actions',[])
+        actions = session_data.setdefault("actions", [])
         actions.append(action)
 
         if len(actions) == 1:
             # it's our first request, we ask the desired new status
             status = XEP_0050.STATUS.EXECUTING
-            form = data_form.Form('form', title=_('status selection'))
-            show_options = [data_form.Option(name, label) for name, label in SHOWS.items()]
-            field = data_form.Field('list-single', 'show', options=show_options, required=True)
+            form = data_form.Form("form", title=_("status selection"))
+            show_options = [
+                data_form.Option(name, label) for name, label in SHOWS.items()
+            ]
+            field = data_form.Field(
+                "list-single", "show", options=show_options, required=True
+            )
             form.addField(field)
 
             payload = form.toElement()
@@ -440,9 +552,9 @@
         elif len(actions) == 2:
             # we should have the answer here
             try:
-                x_elt = command_elt.elements(data_form.NS_X_DATA,'x').next()
+                x_elt = command_elt.elements(data_form.NS_X_DATA, "x").next()
                 answer_form = data_form.Form.fromElement(x_elt)
-                show = answer_form['show']
+                show = answer_form["show"]
             except (KeyError, StopIteration):
                 raise AdHocError(XEP_0050.ERROR.BAD_PAYLOAD)
             if show not in SHOWS:
@@ -453,8 +565,8 @@
                 self.host.setPresence(show=show, profile_key=profile)
 
             # job done, we can end the session
-            form = data_form.Form('form', title=_(u'Updated'))
-            form.addField(data_form.Field('fixed', u'Status updated'))
+            form = data_form.Form("form", title=_(u"Updated"))
+            form.addField(data_form.Field("fixed", u"Status updated"))
             status = XEP_0050.STATUS.COMPLETED
             payload = None
             note = (self.NOTE.INFO, _(u"Status updated"))
@@ -463,7 +575,7 @@
 
         return (payload, status, None, note)
 
-    def _run(self, service_jid_s='', node='', profile_key=C.PROF_KEY_NONE):
+    def _run(self, service_jid_s="", node="", profile_key=C.PROF_KEY_NONE):
         client = self.host.getClient(profile_key)
         service_jid = jid.JID(service_jid_s) if service_jid_s else None
         d = self.run(client, service_jid, node or None)
@@ -483,13 +595,15 @@
         if service_jid is None:
             service_jid = jid.JID(client.jid.host)
         session_id, session_data = self.requesting.newSession(profile=client.profile)
-        session_data['jid'] = service_jid
+        session_data["jid"] = service_jid
         if node is None:
             xmlui = yield self.list(client, service_jid)
         else:
-            session_data['node'] = node
-            cb_data = yield self.requestingEntity({'session_id': session_id}, client.profile)
-            xmlui = cb_data['xmlui']
+            session_data["node"] = node
+            cb_data = yield self.requestingEntity(
+                {"session_id": session_id}, client.profile
+            )
+            xmlui = cb_data["xmlui"]
 
         xmlui.session_id = session_id
         defer.returnValue(xmlui)
@@ -512,8 +626,20 @@
         d.addCallback(self._items2XMLUI, no_instructions)
         return d
 
-    def addAdHocCommand(self, callback, label, node=None, features=None, timeout=600, allowed_jids=None, allowed_groups=None,
-                        allowed_magics=None, forbidden_jids=None, forbidden_groups=None, profile_key=C.PROF_KEY_NONE):
+    def addAdHocCommand(
+        self,
+        callback,
+        label,
+        node=None,
+        features=None,
+        timeout=600,
+        allowed_jids=None,
+        allowed_groups=None,
+        allowed_magics=None,
+        forbidden_jids=None,
+        forbidden_groups=None,
+        profile_key=C.PROF_KEY_NONE,
+    ):
         """Add an ad-hoc command for the current profile
 
         @param callback: method associated with this ad-hoc command which return the payload data (see AdHocCommand._sendAnswer), can return a deferred
@@ -534,7 +660,7 @@
         # FIXME: "@ALL@" for profile_key seems useless and dangerous
 
         if node is None:
-            node = "%s_%s" % ('COMMANDS', uuid4())
+            node = "%s_%s" % ("COMMANDS", uuid4())
 
         if features is None:
             features = [data_form.NS_X_DATA]
@@ -544,30 +670,46 @@
         if allowed_groups is None:
             allowed_groups = []
         if allowed_magics is None:
-            allowed_magics = ['@PROFILE_BAREJID@']
+            allowed_magics = ["@PROFILE_BAREJID@"]
         if forbidden_jids is None:
             forbidden_jids = []
         if forbidden_groups is None:
             forbidden_groups = []
 
         for client in self.host.getClients(profile_key):
-            #TODO: manage newly created/removed profiles
-            _allowed_jids = (allowed_jids + [client.jid.userhostJID()]) if '@PROFILE_BAREJID@' in allowed_magics else allowed_jids
-            ad_hoc_command = AdHocCommand(self, callback, label, node, features, timeout, _allowed_jids,
-                                          allowed_groups, allowed_magics, forbidden_jids, forbidden_groups, client)
+            # TODO: manage newly created/removed profiles
+            _allowed_jids = (
+                (allowed_jids + [client.jid.userhostJID()])
+                if "@PROFILE_BAREJID@" in allowed_magics
+                else allowed_jids
+            )
+            ad_hoc_command = AdHocCommand(
+                self,
+                callback,
+                label,
+                node,
+                features,
+                timeout,
+                _allowed_jids,
+                allowed_groups,
+                allowed_magics,
+                forbidden_jids,
+                forbidden_groups,
+                client,
+            )
             ad_hoc_command.setHandlerParent(client)
             profile_commands = self.answering.setdefault(client.profile, {})
             profile_commands[node] = ad_hoc_command
 
     def onCmdRequest(self, request, profile):
         request.handled = True
-        requestor = jid.JID(request['from'])
-        command_elt = request.elements(NS_COMMANDS, 'command').next()
-        action = command_elt.getAttribute('action', self.ACTION.EXECUTE)
-        node = command_elt.getAttribute('node')
+        requestor = jid.JID(request["from"])
+        command_elt = request.elements(NS_COMMANDS, "command").next()
+        action = command_elt.getAttribute("action", self.ACTION.EXECUTE)
+        node = command_elt.getAttribute("node")
         if not node:
             raise exceptions.DataError
-        sessionid = command_elt.getAttribute('sessionid')
+        sessionid = command_elt.getAttribute("sessionid")
         try:
             command = self.answering[profile][node]
         except KeyError:
@@ -582,18 +724,26 @@
         self.plugin_parent = plugin_parent
 
     def connectionInitialized(self):
-        self.xmlstream.addObserver(CMD_REQUEST, self.plugin_parent.onCmdRequest, profile=self.parent.profile)
+        self.xmlstream.addObserver(
+            CMD_REQUEST, self.plugin_parent.onCmdRequest, profile=self.parent.profile
+        )
 
-    def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
+    def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
         identities = []
-        if nodeIdentifier == NS_COMMANDS and self.plugin_parent.answering.get(self.parent.profile): # we only add the identity if we have registred commands
+        if nodeIdentifier == NS_COMMANDS and self.plugin_parent.answering.get(
+            self.parent.profile
+        ):  # we only add the identity if we have registred commands
             identities.append(ID_CMD_LIST)
         return [disco.DiscoFeature(NS_COMMANDS)] + identities
 
-    def getDiscoItems(self, requestor, target, nodeIdentifier=''):
+    def getDiscoItems(self, requestor, target, nodeIdentifier=""):
         ret = []
         if nodeIdentifier == NS_COMMANDS:
-            for command in self.plugin_parent.answering.get(self.parent.profile,{}).values():
+            for command in self.plugin_parent.answering.get(
+                self.parent.profile, {}
+            ).values():
                 if command.isAuthorised(requestor):
-                    ret.append(disco.DiscoItem(self.parent.jid, command.node, command.getName())) #TODO: manage name language
+                    ret.append(
+                        disco.DiscoItem(self.parent.jid, command.node, command.getName())
+                    )  # TODO: manage name language
         return ret
--- a/sat/plugins/plugin_xep_0054.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_xep_0054.py	Wed Jun 27 20:14:46 2018 +0200
@@ -21,6 +21,7 @@
 from sat.core.i18n import _
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from twisted.internet import threads, defer
 from twisted.words.protocols.jabber import jid, error
@@ -36,10 +37,13 @@
 from sat.core import exceptions
 from sat.memory import persistent
 import mimetypes
+
 try:
     from PIL import Image
 except:
-    raise exceptions.MissingModule(u"Missing module pillow, please download/install it from https://python-pillow.github.io")
+    raise exceptions.MissingModule(
+        u"Missing module pillow, please download/install it from https://python-pillow.github.io"
+    )
 from cStringIO import StringIO
 
 try:
@@ -48,17 +52,17 @@
     from wokkel.subprotocols import XMPPHandler
 
 AVATAR_PATH = "avatars"
-AVATAR_DIM = (64, 64)  # FIXME: dim are not adapted to modern resolutions !
+AVATAR_DIM = (64, 64)  #  FIXME: dim are not adapted to modern resolutions !
 
 IQ_GET = '/iq[@type="get"]'
-NS_VCARD = 'vcard-temp'
+NS_VCARD = "vcard-temp"
 VCARD_REQUEST = IQ_GET + '/vCard[@xmlns="' + NS_VCARD + '"]'  # TODO: manage requests
 
-PRESENCE = '/presence'
-NS_VCARD_UPDATE = 'vcard-temp:x:update'
+PRESENCE = "/presence"
+NS_VCARD_UPDATE = "vcard-temp:x:update"
 VCARD_UPDATE = PRESENCE + '/x[@xmlns="' + NS_VCARD_UPDATE + '"]'
 
-CACHED_DATA = {'avatar', 'nick'}
+CACHED_DATA = {"avatar", "nick"}
 MAX_AGE = 60 * 60 * 24 * 365
 
 PLUGIN_INFO = {
@@ -70,20 +74,34 @@
     C.PI_RECOMMENDATIONS: ["XEP-0045"],
     C.PI_MAIN: "XEP_0054",
     C.PI_HANDLER: "yes",
-    C.PI_DESCRIPTION: _("""Implementation of vcard-temp""")
+    C.PI_DESCRIPTION: _("""Implementation of vcard-temp"""),
 }
 
 
 class XEP_0054(object):
-    #TODO: - check that nickname is ok
+    # TODO: - check that nickname is ok
     #      - refactor the code/better use of Wokkel
     #      - get missing values
 
     def __init__(self, host):
         log.info(_(u"Plugin XEP_0054 initialization"))
         self.host = host
-        host.bridge.addMethod(u"avatarGet", u".plugin", in_sign=u'sbbs', out_sign=u's', method=self._getAvatar, async=True)
-        host.bridge.addMethod(u"avatarSet", u".plugin", in_sign=u'ss', out_sign=u'', method=self._setAvatar, async=True)
+        host.bridge.addMethod(
+            u"avatarGet",
+            u".plugin",
+            in_sign=u"sbbs",
+            out_sign=u"s",
+            method=self._getAvatar,
+            async=True,
+        )
+        host.bridge.addMethod(
+            u"avatarSet",
+            u".plugin",
+            in_sign=u"ss",
+            out_sign=u"",
+            method=self._setAvatar,
+            async=True,
+        )
         host.trigger.add(u"presence_available", self.presenceAvailableTrigger)
         host.memory.setSignalOnUpdate(u"avatar")
         host.memory.setSignalOnUpdate(u"nick")
@@ -98,7 +116,7 @@
         @return (bool): True if the bare jid of the entity is a room jid
         """
         try:
-            muc_plg = self.host.plugins['XEP-0045']
+            muc_plg = self.host.plugins["XEP-0045"]
         except KeyError:
             return False
 
@@ -123,12 +141,12 @@
     def presenceAvailableTrigger(self, presence_elt, client):
         if client.jid.userhost() in client._cache_0054:
             try:
-                avatar_hash = client._cache_0054[client.jid.userhost()]['avatar']
+                avatar_hash = client._cache_0054[client.jid.userhost()]["avatar"]
             except KeyError:
                 log.info(u"No avatar in cache for {}".format(client.jid.userhost()))
                 return True
-            x_elt = domish.Element((NS_VCARD_UPDATE, 'x'))
-            x_elt.addElement('photo', content=avatar_hash)
+            x_elt = domish.Element((NS_VCARD_UPDATE, "x"))
+            x_elt.addElement("photo", content=avatar_hash)
             presence_elt.addChild(x_elt)
         return True
 
@@ -139,7 +157,7 @@
         self._fillCachedValues(client.profile)
 
     def _fillCachedValues(self, profile):
-        #FIXME: this may need to be reworked
+        # FIXME: this may need to be reworked
         #       the current naive approach keeps a map between all jids
         #       in persistent cache, then put avatar hashs in memory.
         #       Hashes should be shared between profiles (or not ? what
@@ -153,9 +171,15 @@
                 try:
                     value = data[name]
                     if value is None:
-                        log.error(u"{name} value for {jid_} is None, ignoring".format(name=name, jid_=jid_))
+                        log.error(
+                            u"{name} value for {jid_} is None, ignoring".format(
+                                name=name, jid_=jid_
+                            )
+                        )
                         continue
-                    self.host.memory.updateEntityData(jid_, name, data[name], silent=True, profile_key=profile)
+                    self.host.memory.updateEntityData(
+                        jid_, name, data[name], silent=True, profile_key=profile
+                    )
                 except KeyError:
                     pass
 
@@ -184,7 +208,9 @@
                 else:
                     client._cache_0054.force(jid_s)
         else:
-            self.host.memory.updateEntityData(jid_, name, value, profile_key=client.profile)
+            self.host.memory.updateEntityData(
+                jid_, name, value, profile_key=client.profile
+            )
             if name in CACHED_DATA:
                 client._cache_0054.setdefault(jid_s, {})[name] = value
                 client._cache_0054.force(jid_s)
@@ -206,7 +232,7 @@
         """Parse a <PHOTO> photo_elt and save the picture"""
         # XXX: this method is launched in a separate thread
         try:
-            mime_type = unicode(photo_elt.elements(NS_VCARD, 'TYPE').next())
+            mime_type = unicode(photo_elt.elements(NS_VCARD, "TYPE").next())
         except StopIteration:
             log.warning(u"no MIME type found, assuming image/png")
             mime_type = u"image/png"
@@ -220,35 +246,41 @@
                     mime_type = "image/png"
                 else:
                     # TODO: handle other image formats (svg?)
-                    log.warning(u"following avatar image format is not handled: {type} [{jid}]".format(
-                        type=mime_type, jid=entity_jid.full()))
+                    log.warning(
+                        u"following avatar image format is not handled: {type} [{jid}]".format(
+                            type=mime_type, jid=entity_jid.full()
+                        )
+                    )
                     raise Failure(exceptions.DataError())
 
             ext = mimetypes.guess_extension(mime_type, strict=False)
             assert ext is not None
-            if ext == u'.jpe':
-                ext = u'.jpg'
-            log.debug(u'photo of type {type} with extension {ext} found [{jid}]'.format(
-                    type=mime_type, ext=ext, jid=entity_jid.full()))
+            if ext == u".jpe":
+                ext = u".jpg"
+            log.debug(
+                u"photo of type {type} with extension {ext} found [{jid}]".format(
+                    type=mime_type, ext=ext, jid=entity_jid.full()
+                )
+            )
         try:
-            buf = str(photo_elt.elements(NS_VCARD, 'BINVAL').next())
+            buf = str(photo_elt.elements(NS_VCARD, "BINVAL").next())
         except StopIteration:
             log.warning(u"BINVAL element not found")
             raise Failure(exceptions.NotFound())
         if not buf:
             log.warning(u"empty avatar for {jid}".format(jid=entity_jid.full()))
             raise Failure(exceptions.NotFound())
-        log.debug(_(u'Decoding binary'))
+        log.debug(_(u"Decoding binary"))
         decoded = b64decode(buf)
         del buf
         image_hash = sha1(decoded).hexdigest()
         with client.cache.cacheData(
-            PLUGIN_INFO['import_name'],
+            PLUGIN_INFO["import_name"],
             image_hash,
             mime_type,
             # we keep in cache for 1 year
-            MAX_AGE
-            ) as f:
+            MAX_AGE,
+        ) as f:
             f.write(decoded)
         return image_hash
 
@@ -259,39 +291,44 @@
         vcard_dict = {}
 
         for elem in vcard.elements():
-            if elem.name == 'FN':
-                vcard_dict['fullname'] = unicode(elem)
-            elif elem.name == 'NICKNAME':
-                vcard_dict['nick'] = unicode(elem)
-                self.updateCache(client, entity_jid, 'nick', vcard_dict['nick'])
-            elif elem.name == 'URL':
-                vcard_dict['website'] = unicode(elem)
-            elif elem.name == 'EMAIL':
-                vcard_dict['email'] = unicode(elem)
-            elif elem.name == 'BDAY':
-                vcard_dict['birthday'] = unicode(elem)
-            elif elem.name == 'PHOTO':
+            if elem.name == "FN":
+                vcard_dict["fullname"] = unicode(elem)
+            elif elem.name == "NICKNAME":
+                vcard_dict["nick"] = unicode(elem)
+                self.updateCache(client, entity_jid, "nick", vcard_dict["nick"])
+            elif elem.name == "URL":
+                vcard_dict["website"] = unicode(elem)
+            elif elem.name == "EMAIL":
+                vcard_dict["email"] = unicode(elem)
+            elif elem.name == "BDAY":
+                vcard_dict["birthday"] = unicode(elem)
+            elif elem.name == "PHOTO":
                 # TODO: handle EXTVAL
                 try:
                     avatar_hash = yield threads.deferToThread(
-                        self.savePhoto, client, elem, entity_jid)
+                        self.savePhoto, client, elem, entity_jid
+                    )
                 except (exceptions.DataError, exceptions.NotFound) as e:
-                    avatar_hash = ''
-                    vcard_dict['avatar'] = avatar_hash
+                    avatar_hash = ""
+                    vcard_dict["avatar"] = avatar_hash
                 except Exception as e:
                     log.error(u"avatar saving error: {}".format(e))
                     avatar_hash = None
                 else:
-                    vcard_dict['avatar'] = avatar_hash
-                self.updateCache(client, entity_jid, 'avatar', avatar_hash)
+                    vcard_dict["avatar"] = avatar_hash
+                self.updateCache(client, entity_jid, "avatar", avatar_hash)
             else:
-                log.debug(u'FIXME: [{}] VCard tag is not managed yet'.format(elem.name))
+                log.debug(u"FIXME: [{}] VCard tag is not managed yet".format(elem.name))
 
         # if a data in cache doesn't exist anymore, we need to delete it
         # so we check CACHED_DATA no gotten (i.e. not in vcard_dict keys)
         # and we reset them
         for datum in CACHED_DATA.difference(vcard_dict.keys()):
-            log.debug(u"reseting vcard datum [{datum}] for {entity}".format(datum=datum, entity=entity_jid.full()))
+            log.debug(
+                u"reseting vcard datum [{datum}] for {entity}".format(
+                    datum=datum, entity=entity_jid.full()
+                )
+            )
             self.updateCache(client, entity_jid, datum, None)
 
         defer.returnValue(vcard_dict)
@@ -309,11 +346,15 @@
 
     def _vCardEb(self, failure_, to_jid, client):
         """Called when something is wrong with registration"""
-        log.warning(u"Can't get vCard for {jid}: {failure}".format(jid=to_jid.full, failure=failure_))
+        log.warning(
+            u"Can't get vCard for {jid}: {failure}".format(
+                jid=to_jid.full, failure=failure_
+            )
+        )
         self.updateCache(client, to_jid, "avatar", None)
 
     def _getVcardElt(self, iq_elt):
-       return iq_elt.elements(NS_VCARD, "vCard").next()
+        return iq_elt.elements(NS_VCARD, "vCard").next()
 
     def getCardRaw(self, client, entity_jid):
         """get raw vCard XML
@@ -322,10 +363,10 @@
         """
         entity_jid = self.getBareOrFull(client, entity_jid)
         log.debug(u"Asking for {}'s VCard".format(entity_jid.full()))
-        reg_request = client.IQ('get')
+        reg_request = client.IQ("get")
         reg_request["from"] = client.jid.full()
         reg_request["to"] = entity_jid.full()
-        reg_request.addElement('vCard', NS_VCARD)
+        reg_request.addElement("vCard", NS_VCARD)
         d = reg_request.send(entity_jid.full())
         d.addCallback(self._getVcardElt)
         return d
@@ -337,19 +378,24 @@
         @result: id to retrieve the profile
         """
         d = self.getCardRaw(client, entity_jid)
-        d.addCallbacks(self._vCardCb, self._vCardEb, callbackArgs=[entity_jid, client], errbackArgs=[entity_jid, client])
+        d.addCallbacks(
+            self._vCardCb,
+            self._vCardEb,
+            callbackArgs=[entity_jid, client],
+            errbackArgs=[entity_jid, client],
+        )
         return d
 
     def _getCardCb(self, dummy, client, entity):
         try:
-            return client._cache_0054[entity.full()]['avatar']
+            return client._cache_0054[entity.full()]["avatar"]
         except KeyError:
             raise Failure(exceptions.NotFound())
 
     def _getAvatar(self, entity, cache_only, hash_only, profile):
         client = self.host.getClient(profile)
         d = self.getAvatar(client, jid.JID(entity), cache_only, hash_only)
-        d.addErrback(lambda dummy: '')
+        d.addErrback(lambda dummy: "")
 
         return d
 
@@ -370,7 +416,7 @@
 
         try:
             # we first check if we have avatar in cache
-            avatar_hash = client._cache_0054[entity.full()]['avatar']
+            avatar_hash = client._cache_0054[entity.full()]["avatar"]
             if avatar_hash:
                 # avatar is known and exists
                 full_path = client.cache.getFilePath(avatar_hash)
@@ -379,7 +425,7 @@
                     raise KeyError
             else:
                 # avatar has already been checked but it is not set
-                full_path = u''
+                full_path = u""
         except KeyError:
             # avatar is not in cache
             if cache_only:
@@ -406,11 +452,11 @@
         @param entity(jid.JID): entity to get nick from
         @return(unicode, None): nick or None if not found
         """
-        nick = self.getCache(client, entity, u'nick')
+        nick = self.getCache(client, entity, u"nick")
         if nick is not None:
             defer.returnValue(nick)
         yield self.getCard(client, entity)
-        defer.returnValue(self.getCache(client, entity, u'nick'))
+        defer.returnValue(self.getCache(client, entity, u"nick"))
 
     @defer.inlineCallbacks
     def setNick(self, client, nick):
@@ -422,22 +468,22 @@
         try:
             vcard_elt = yield self.getCardRaw(client, jid_)
         except error.StanzaError as e:
-            if e.condition == 'item-not-found':
-                vcard_elt = domish.Element((NS_VCARD, 'vCard'))
+            if e.condition == "item-not-found":
+                vcard_elt = domish.Element((NS_VCARD, "vCard"))
             else:
                 raise e
         try:
-            nickname_elt = next(vcard_elt.elements(NS_VCARD, u'NICKNAME'))
+            nickname_elt = next(vcard_elt.elements(NS_VCARD, u"NICKNAME"))
         except StopIteration:
             pass
         else:
             vcard_elt.children.remove(nickname_elt)
 
-        nickname_elt = vcard_elt.addElement((NS_VCARD, u'NICKNAME'), content=nick)
+        nickname_elt = vcard_elt.addElement((NS_VCARD, u"NICKNAME"), content=nick)
         iq_elt = client.IQ()
         vcard_elt = iq_elt.addChild(vcard_elt)
         yield iq_elt.send()
-        self.updateCache(client, jid_, u'nick', unicode(nick))
+        self.updateCache(client, jid_, u"nick", unicode(nick))
 
     def _buildSetAvatar(self, client, vcard_elt, file_path):
         # XXX: this method is executed in a separate thread
@@ -460,18 +506,15 @@
                     right -= offset
                 img = img.crop((left, upper, right, lower))
         img_buf = StringIO()
-        img.save(img_buf, 'PNG')
+        img.save(img_buf, "PNG")
 
-        photo_elt = vcard_elt.addElement('PHOTO')
-        photo_elt.addElement('TYPE', content='image/png')
-        photo_elt.addElement('BINVAL', content=b64encode(img_buf.getvalue()))
+        photo_elt = vcard_elt.addElement("PHOTO")
+        photo_elt.addElement("TYPE", content="image/png")
+        photo_elt.addElement("BINVAL", content=b64encode(img_buf.getvalue()))
         image_hash = sha1(img_buf.getvalue()).hexdigest()
         with client.cache.cacheData(
-            PLUGIN_INFO['import_name'],
-            image_hash,
-            "image/png",
-            MAX_AGE
-            ) as f:
+            PLUGIN_INFO["import_name"], image_hash, "image/png", MAX_AGE
+        ) as f:
             f.write(img_buf.getvalue())
         return image_hash
 
@@ -489,14 +532,14 @@
             # we first check if a vcard already exists, to keep data
             vcard_elt = yield self.getCardRaw(client, client.jid.userhostJID())
         except error.StanzaError as e:
-            if e.condition == 'item-not-found':
-                vcard_elt = domish.Element((NS_VCARD, 'vCard'))
+            if e.condition == "item-not-found":
+                vcard_elt = domish.Element((NS_VCARD, "vCard"))
             else:
                 raise e
         else:
             # the vcard exists, we need to remove PHOTO element as we'll make a new one
             try:
-                photo_elt = next(vcard_elt.elements(NS_VCARD, u'PHOTO'))
+                photo_elt = next(vcard_elt.elements(NS_VCARD, u"PHOTO"))
             except StopIteration:
                 pass
             else:
@@ -504,12 +547,14 @@
 
         iq_elt = client.IQ()
         iq_elt.addChild(vcard_elt)
-        image_hash = yield threads.deferToThread(self._buildSetAvatar, client, vcard_elt, file_path)
+        image_hash = yield threads.deferToThread(
+            self._buildSetAvatar, client, vcard_elt, file_path
+        )
         # image is now at the right size/format
 
-        self.updateCache(client, client.jid.userhostJID(), 'avatar', image_hash)
+        self.updateCache(client, client.jid.userhostJID(), "avatar", image_hash)
         yield iq_elt.send()
-        client.presence.available() # FIXME: should send the current presence, not always "available" !
+        client.presence.available()  # FIXME: should send the current presence, not always "available" !
 
 
 class XEP_0054_handler(XMPPHandler):
@@ -522,21 +567,24 @@
     def connectionInitialized(self):
         self.xmlstream.addObserver(VCARD_UPDATE, self.update)
 
-    def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
+    def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
         return [disco.DiscoFeature(NS_VCARD)]
 
-    def getDiscoItems(self, requestor, target, nodeIdentifier=''):
+    def getDiscoItems(self, requestor, target, nodeIdentifier=""):
         return []
 
     def _checkAvatarHash(self, dummy, client, entity, given_hash):
         """check that hash in cash (i.e. computed hash) is the same as given one"""
         # XXX: if they differ, the avater will be requested on each connection
         # TODO: try to avoid re-requesting avatar in this case
-        computed_hash = self.plugin_parent.getCache(client, entity, 'avatar')
+        computed_hash = self.plugin_parent.getCache(client, entity, "avatar")
         if computed_hash != given_hash:
-            log.warning(u"computed hash differs from given hash for {entity}:\n"
+            log.warning(
+                u"computed hash differs from given hash for {entity}:\n"
                 "computed: {computed}\ngiven: {given}".format(
-                entity=entity, computed=computed_hash, given=given_hash))
+                    entity=entity, computed=computed_hash, given=given_hash
+                )
+            )
 
     def update(self, presence):
         """Called on <presence/> stanza with vcard data
@@ -545,22 +593,22 @@
         @param presend(domish.Element): <presence/> stanza
         """
         client = self.parent
-        entity_jid = self.plugin_parent.getBareOrFull(client, jid.JID(presence['from']))
-        #FIXME: wokkel's data_form should be used here
+        entity_jid = self.plugin_parent.getBareOrFull(client, jid.JID(presence["from"]))
+        # FIXME: wokkel's data_form should be used here
         try:
-            x_elt = presence.elements(NS_VCARD_UPDATE, 'x').next()
+            x_elt = presence.elements(NS_VCARD_UPDATE, "x").next()
         except StopIteration:
             return
 
         try:
-            photo_elt = x_elt.elements(NS_VCARD_UPDATE, 'photo').next()
+            photo_elt = x_elt.elements(NS_VCARD_UPDATE, "photo").next()
         except StopIteration:
             return
 
         hash_ = unicode(photo_elt).strip()
         if hash_ == C.HASH_SHA1_EMPTY:
-            hash_ = u''
-        old_avatar = self.plugin_parent.getCache(client, entity_jid, 'avatar')
+            hash_ = u""
+        old_avatar = self.plugin_parent.getCache(client, entity_jid, "avatar")
 
         if old_avatar == hash_:
             # no change, we can return...
@@ -568,7 +616,11 @@
                 # ...but we double check that avatar is in cache
                 file_path = client.cache.getFilePath(hash_)
                 if file_path is None:
-                    log.error(u"Avatar for [{}] should be in cache but it is not! We get it".format(entity_jid.full()))
+                    log.error(
+                        u"Avatar for [{}] should be in cache but it is not! We get it".format(
+                            entity_jid.full()
+                        )
+                    )
                     self.plugin_parent.getCard(client, entity_jid)
             else:
                 log.debug(u"avatar for {} already in cache".format(entity_jid.full()))
@@ -578,14 +630,20 @@
             # the avatar has been removed
             # XXX: we use empty string instead of None to indicate that we took avatar
             #      but it is empty on purpose
-            self.plugin_parent.updateCache(client, entity_jid, 'avatar', '')
+            self.plugin_parent.updateCache(client, entity_jid, "avatar", "")
             return
 
         file_path = client.cache.getFilePath(hash_)
         if file_path is not None:
-            log.debug(u"New avatar found for [{}], it's already in cache, we use it".format(entity_jid.full()))
-            self.plugin_parent.updateCache(client, entity_jid, 'avatar', hash_)
+            log.debug(
+                u"New avatar found for [{}], it's already in cache, we use it".format(
+                    entity_jid.full()
+                )
+            )
+            self.plugin_parent.updateCache(client, entity_jid, "avatar", hash_)
         else:
-            log.debug(u'New avatar found for [{}], requesting vcard'.format(entity_jid.full()))
+            log.debug(
+                u"New avatar found for [{}], requesting vcard".format(entity_jid.full())
+            )
             d = self.plugin_parent.getCard(client, entity_jid)
             d.addCallback(self._checkAvatarHash, client, entity_jid, hash_)
--- a/sat/plugins/plugin_xep_0055.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_xep_0055.py	Wed Jun 27 20:14:46 2018 +0200
@@ -19,6 +19,7 @@
 
 from sat.core.i18n import _, D_
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 
 from twisted.words.protocols.jabber.xmlstream import IQ
@@ -30,6 +31,7 @@
 from sat.tools import xml_tools
 
 from wokkel import disco, iwokkel
+
 try:
     from twisted.words.protocols.xmlstream import XMPPHandler
 except ImportError:
@@ -37,7 +39,7 @@
 from zope.interface import implements
 
 
-NS_SEARCH = 'jabber:iq:search'
+NS_SEARCH = "jabber:iq:search"
 
 PLUGIN_INFO = {
     C.PI_NAME: "Jabber Search",
@@ -48,7 +50,7 @@
     C.PI_RECOMMENDATIONS: ["XEP-0059"],
     C.PI_MAIN: "XEP_0055",
     C.PI_HANDLER: "no",
-    C.PI_DESCRIPTION: _("""Implementation of Jabber Search""")
+    C.PI_DESCRIPTION: _("""Implementation of Jabber Search"""),
 }
 
 # config file parameters
@@ -58,26 +60,48 @@
 DEFAULT_SERVICE_LIST = ["salut.libervia.org"]
 
 FIELD_SINGLE = "field_single"  # single text field for the simple search
-FIELD_CURRENT_SERVICE = "current_service_jid"  # read-only text field for the advanced search
+FIELD_CURRENT_SERVICE = (
+    "current_service_jid"
+)  # read-only text field for the advanced search
+
 
 class XEP_0055(object):
-
     def __init__(self, host):
         log.info(_("Jabber search plugin initialization"))
         self.host = host
 
         # default search services (config file + hard-coded lists)
-        self.services = [jid.JID(entry) for entry in host.memory.getConfig(CONFIG_SECTION, CONFIG_SERVICE_LIST, DEFAULT_SERVICE_LIST)]
+        self.services = [
+            jid.JID(entry)
+            for entry in host.memory.getConfig(
+                CONFIG_SECTION, CONFIG_SERVICE_LIST, DEFAULT_SERVICE_LIST
+            )
+        ]
 
-        host.bridge.addMethod("searchGetFieldsUI", ".plugin", in_sign='ss', out_sign='s',
-                              method=self._getFieldsUI,
-                              async=True)
-        host.bridge.addMethod("searchRequest", ".plugin", in_sign='sa{ss}s', out_sign='s',
-                              method=self._searchRequest,
-                              async=True)
+        host.bridge.addMethod(
+            "searchGetFieldsUI",
+            ".plugin",
+            in_sign="ss",
+            out_sign="s",
+            method=self._getFieldsUI,
+            async=True,
+        )
+        host.bridge.addMethod(
+            "searchRequest",
+            ".plugin",
+            in_sign="sa{ss}s",
+            out_sign="s",
+            method=self._searchRequest,
+            async=True,
+        )
 
         self.__search_menu_id = host.registerCallback(self._getMainUI, with_data=True)
-        host.importMenu((D_("Contacts"), D_("Search directory")), self._getMainUI, security_limit=1, help_string=D_("Search user directory"))
+        host.importMenu(
+            (D_("Contacts"), D_("Search directory")),
+            self._getMainUI,
+            security_limit=1,
+            help_string=D_("Search user directory"),
+        )
 
     def _getHostServices(self, profile):
         """Return the jabber search services associated to the user host.
@@ -89,10 +113,8 @@
         d = self.host.findFeaturesSet(client, [NS_SEARCH])
         return d.addCallback(lambda set_: list(set_))
 
-
     ## Main search UI (menu item callback) ##
 
-
     def _getMainUI(self, raw_data, profile):
         """Get the XMLUI for selecting a service and searching the directory.
 
@@ -115,11 +137,18 @@
         # extend services offered by user's server with the default services
         services.extend([service for service in self.services if service not in services])
         data = xml_tools.XMLUIResult2DataFormResult(raw_data)
-        main_ui = xml_tools.XMLUI(C.XMLUI_WINDOW, container="tabs", title=_("Search users"), submit_id=self.__search_menu_id)
+        main_ui = xml_tools.XMLUI(
+            C.XMLUI_WINDOW,
+            container="tabs",
+            title=_("Search users"),
+            submit_id=self.__search_menu_id,
+        )
 
         d = self._addSimpleSearchUI(services, main_ui, data, profile)
-        d.addCallback(lambda dummy: self._addAdvancedSearchUI(services, main_ui, data, profile))
-        return d.addCallback(lambda dummy: {'xmlui': main_ui.toXml()})
+        d.addCallback(
+            lambda dummy: self._addAdvancedSearchUI(services, main_ui, data, profile)
+        )
+        return d.addCallback(lambda dummy: {"xmlui": main_ui.toXml()})
 
     def _addSimpleSearchUI(self, services, main_ui, data, profile):
         """Add to the main UI a tab for the simple search.
@@ -133,32 +162,49 @@
 
         @return: a dummy Deferred
         """
-        service_jid = services[0]  # TODO: search on all the given services, not only the first one
+        service_jid = services[
+            0
+        ]  # TODO: search on all the given services, not only the first one
 
-        form = data_form.Form('form', formNamespace=NS_SEARCH)
-        form.addField(data_form.Field('text-single', FIELD_SINGLE, label=_('Search for'), value=data.get(FIELD_SINGLE, '')))
+        form = data_form.Form("form", formNamespace=NS_SEARCH)
+        form.addField(
+            data_form.Field(
+                "text-single",
+                FIELD_SINGLE,
+                label=_("Search for"),
+                value=data.get(FIELD_SINGLE, ""),
+            )
+        )
 
-        sub_cont = main_ui.main_container.addTab("simple_search", label=_("Simple search"), container=xml_tools.VerticalContainer)
+        sub_cont = main_ui.main_container.addTab(
+            "simple_search",
+            label=_("Simple search"),
+            container=xml_tools.VerticalContainer,
+        )
         main_ui.changeContainer(sub_cont.append(xml_tools.PairsContainer(main_ui)))
         xml_tools.dataForm2Widgets(main_ui, form)
 
         # FIXME: add colspan attribute to divider? (we are in a PairsContainer)
-        main_ui.addDivider('blank')
-        main_ui.addDivider('blank')  # here we added a blank line before the button
-        main_ui.addDivider('blank')
+        main_ui.addDivider("blank")
+        main_ui.addDivider("blank")  # here we added a blank line before the button
+        main_ui.addDivider("blank")
         main_ui.addButton(self.__search_menu_id, _("Search"), (FIELD_SINGLE,))
-        main_ui.addDivider('blank')
-        main_ui.addDivider('blank')  # a blank line again after the button
+        main_ui.addDivider("blank")
+        main_ui.addDivider("blank")  # a blank line again after the button
 
-        simple_data = {key: value for key, value in data.iteritems() if key in (FIELD_SINGLE,)}
+        simple_data = {
+            key: value for key, value in data.iteritems() if key in (FIELD_SINGLE,)
+        }
         if simple_data:
             log.debug("Simple search with %s on %s" % (simple_data, service_jid))
             sub_cont.parent.setSelected(True)
             main_ui.changeContainer(sub_cont.append(xml_tools.VerticalContainer(main_ui)))
-            main_ui.addDivider('dash')
+            main_ui.addDivider("dash")
             d = self.searchRequest(service_jid, simple_data, profile)
-            d.addCallbacks(lambda elt: self._displaySearchResult(main_ui, elt),
-                           lambda failure: main_ui.addText(failure.getErrorMessage()))
+            d.addCallbacks(
+                lambda elt: self._displaySearchResult(main_ui, elt),
+                lambda failure: main_ui.addText(failure.getErrorMessage()),
+            )
             return d
 
         return defer.succeed(None)
@@ -175,16 +221,20 @@
 
         @return: a dummy Deferred
         """
-        sub_cont = main_ui.main_container.addTab("advanced_search", label=_("Advanced search"), container=xml_tools.VerticalContainer)
-        service_selection_fields = ['service_jid', 'service_jid_extra']
+        sub_cont = main_ui.main_container.addTab(
+            "advanced_search",
+            label=_("Advanced search"),
+            container=xml_tools.VerticalContainer,
+        )
+        service_selection_fields = ["service_jid", "service_jid_extra"]
 
-        if 'service_jid_extra' in data:
+        if "service_jid_extra" in data:
             # refresh button has been pushed, select the tab
             sub_cont.parent.setSelected(True)
             # get the selected service
-            service_jid_s = data.get('service_jid_extra', '')
+            service_jid_s = data.get("service_jid_extra", "")
             if not service_jid_s:
-                service_jid_s = data.get('service_jid', unicode(services[0]))
+                service_jid_s = data.get("service_jid", unicode(services[0]))
             log.debug("Refreshing search fields for %s" % service_jid_s)
         else:
             service_jid_s = data.get(FIELD_CURRENT_SERVICE, unicode(services[0]))
@@ -194,27 +244,32 @@
 
         main_ui.changeContainer(sub_cont.append(xml_tools.PairsContainer(main_ui)))
         main_ui.addLabel(_("Search on"))
-        main_ui.addList('service_jid', options=services_s, selected=service_jid_s)
+        main_ui.addList("service_jid", options=services_s, selected=service_jid_s)
         main_ui.addLabel(_("Other service"))
-        main_ui.addString(name='service_jid_extra')
+        main_ui.addString(name="service_jid_extra")
 
         # FIXME: add colspan attribute to divider? (we are in a PairsContainer)
-        main_ui.addDivider('blank')
-        main_ui.addDivider('blank')  # here we added a blank line before the button
-        main_ui.addDivider('blank')
-        main_ui.addButton(self.__search_menu_id, _("Refresh fields"), service_selection_fields)
-        main_ui.addDivider('blank')
-        main_ui.addDivider('blank')  # a blank line again after the button
+        main_ui.addDivider("blank")
+        main_ui.addDivider("blank")  # here we added a blank line before the button
+        main_ui.addDivider("blank")
+        main_ui.addButton(
+            self.__search_menu_id, _("Refresh fields"), service_selection_fields
+        )
+        main_ui.addDivider("blank")
+        main_ui.addDivider("blank")  # a blank line again after the button
         main_ui.addLabel(_("Displaying the search form for"))
         main_ui.addString(name=FIELD_CURRENT_SERVICE, value=service_jid_s, read_only=True)
-        main_ui.addDivider('dash')
-        main_ui.addDivider('dash')
+        main_ui.addDivider("dash")
+        main_ui.addDivider("dash")
 
         main_ui.changeContainer(sub_cont.append(xml_tools.VerticalContainer(main_ui)))
         service_jid = jid.JID(service_jid_s)
         d = self.getFieldsUI(service_jid, profile)
-        d.addCallbacks(self._addAdvancedForm, lambda failure: main_ui.addText(failure.getErrorMessage()),
-                       [service_jid, main_ui, sub_cont, data, profile])
+        d.addCallbacks(
+            self._addAdvancedForm,
+            lambda failure: main_ui.addText(failure.getErrorMessage()),
+            [service_jid, main_ui, sub_cont, data, profile],
+        )
         return d
 
     def _addAdvancedForm(self, form_elt, service_jid, main_ui, sub_cont, data, profile):
@@ -243,26 +298,29 @@
                 widget.setAttribute("value", adv_data[name])
 
         # FIXME: add colspan attribute to divider? (we are in a PairsContainer)
-        main_ui.addDivider('blank')
-        main_ui.addDivider('blank')  # here we added a blank line before the button
-        main_ui.addDivider('blank')
-        main_ui.addButton(self.__search_menu_id, _("Search"), adv_fields + [FIELD_CURRENT_SERVICE])
-        main_ui.addDivider('blank')
-        main_ui.addDivider('blank')  # a blank line again after the button
+        main_ui.addDivider("blank")
+        main_ui.addDivider("blank")  # here we added a blank line before the button
+        main_ui.addDivider("blank")
+        main_ui.addButton(
+            self.__search_menu_id, _("Search"), adv_fields + [FIELD_CURRENT_SERVICE]
+        )
+        main_ui.addDivider("blank")
+        main_ui.addDivider("blank")  # a blank line again after the button
 
         if adv_data:  # display the search results
             log.debug("Advanced search with %s on %s" % (adv_data, service_jid))
             sub_cont.parent.setSelected(True)
             main_ui.changeContainer(sub_cont.append(xml_tools.VerticalContainer(main_ui)))
-            main_ui.addDivider('dash')
+            main_ui.addDivider("dash")
             d = self.searchRequest(service_jid, adv_data, profile)
-            d.addCallbacks(lambda elt: self._displaySearchResult(main_ui, elt),
-                           lambda failure: main_ui.addText(failure.getErrorMessage()))
+            d.addCallbacks(
+                lambda elt: self._displaySearchResult(main_ui, elt),
+                lambda failure: main_ui.addText(failure.getErrorMessage()),
+            )
             return d
 
         return defer.succeed(None)
 
-
     def _displaySearchResult(self, main_ui, elt):
         """Display the search results.
 
@@ -277,7 +335,9 @@
                     header = headers.keys()[i % len(headers)]
                     widget_type, widget_args, widget_kwargs = xmlui_data[i]
                     value = widget_args[0]
-                    values.setdefault(header, []).append(jid.JID(value) if header == "jid" else value)
+                    values.setdefault(header, []).append(
+                        jid.JID(value) if header == "jid" else value
+                    )
                 main_ui.addJidsList(jids=values["jid"], name=D_(u"Search results"))
                 # TODO: also display the values other than JID
             else:
@@ -285,10 +345,8 @@
         else:
             main_ui.addText(D_("The search gave no result"))
 
-
     ## Retrieve the  search fields ##
 
-
     def _getFieldsUI(self, to_jid_s, profile_key):
         """Ask a service to send us the list of the form fields it manages.
 
@@ -308,10 +366,10 @@
         @return: a deferred domish.Element
         """
         client = self.host.getClient(profile_key)
-        fields_request = IQ(client.xmlstream, 'get')
+        fields_request = IQ(client.xmlstream, "get")
         fields_request["from"] = client.jid.full()
         fields_request["to"] = to_jid.full()
-        fields_request.addElement('query', NS_SEARCH)
+        fields_request.addElement("query", NS_SEARCH)
         d = fields_request.send(to_jid.full())
         d.addCallbacks(self._getFieldsUICb, self._getFieldsUIEb)
         return d
@@ -323,15 +381,17 @@
         @return: domish.Element
         """
         try:
-            query_elts = answer.elements('jabber:iq:search', 'query').next()
+            query_elts = answer.elements("jabber:iq:search", "query").next()
         except StopIteration:
             log.info(_("No query element found"))
             raise DataError  # FIXME: StanzaError is probably more appropriate, check the RFC
         try:
-            form_elt = query_elts.elements(data_form.NS_X_DATA, 'x').next()
+            form_elt = query_elts.elements(data_form.NS_X_DATA, "x").next()
         except StopIteration:
             log.info(_("No data form found"))
-            raise NotImplementedError("Only search through data form is implemented so far")
+            raise NotImplementedError(
+                "Only search through data form is implemented so far"
+            )
         return form_elt
 
     def _getFieldsUIEb(self, failure):
@@ -343,10 +403,8 @@
         log.info(_("Fields request failure: %s") % unicode(failure.getErrorMessage()))
         raise failure
 
-
     ## Do the search ##
 
-
     def _searchRequest(self, to_jid_s, search_data, profile_key):
         """Actually do a search, according to filled data.
 
@@ -370,15 +428,17 @@
         if FIELD_SINGLE in search_data:
             value = search_data[FIELD_SINGLE]
             d = self.getFieldsUI(to_jid, profile_key)
-            d.addCallback(lambda elt: self.searchRequestMulti(to_jid, value, elt, profile_key))
+            d.addCallback(
+                lambda elt: self.searchRequestMulti(to_jid, value, elt, profile_key)
+            )
             return d
 
         client = self.host.getClient(profile_key)
-        search_request = IQ(client.xmlstream, 'set')
+        search_request = IQ(client.xmlstream, "set")
         search_request["from"] = client.jid.full()
         search_request["to"] = to_jid.full()
-        query_elt = search_request.addElement('query', NS_SEARCH)
-        x_form = data_form.Form('submit', formNamespace=NS_SEARCH)
+        query_elt = search_request.addElement("query", NS_SEARCH)
+        x_form = data_form.Form("submit", formNamespace=NS_SEARCH)
         x_form.makeFields(search_data)
         query_elt.addChild(x_form.toElement())
         # TODO: XEP-0059 could be used here (with the needed new method attributes)
@@ -406,13 +466,17 @@
             for success, form_elt in result:
                 if not success:
                     continue
-                if result_elt is None:  # the result element is built over the first answer
+                if (
+                    result_elt is None
+                ):  # the result element is built over the first answer
                     result_elt = form_elt
                     continue
-                for item_elt in form_elt.elements('jabber:x:data', 'item'):
+                for item_elt in form_elt.elements("jabber:x:data", "item"):
                     result_elt.addChild(item_elt)
             if result_elt is None:
-                raise defer.failure.Failure(DataError(_("The search could not be performed")))
+                raise defer.failure.Failure(
+                    DataError(_("The search could not be performed"))
+                )
             return result_elt
 
         return defer.DeferredList(d_list).addCallback(cb)
@@ -424,15 +488,17 @@
         @return: domish.Element
         """
         try:
-            query_elts = answer.elements('jabber:iq:search', 'query').next()
+            query_elts = answer.elements("jabber:iq:search", "query").next()
         except StopIteration:
             log.info(_("No query element found"))
             raise DataError  # FIXME: StanzaError is probably more appropriate, check the RFC
         try:
-            form_elt = query_elts.elements(data_form.NS_X_DATA, 'x').next()
+            form_elt = query_elts.elements(data_form.NS_X_DATA, "x").next()
         except StopIteration:
             log.info(_("No data form found"))
-            raise NotImplementedError("Only search through data form is implemented so far")
+            raise NotImplementedError(
+                "Only search through data form is implemented so far"
+            )
         return form_elt
 
     def _searchErr(self, failure):
@@ -453,9 +519,8 @@
         self.host = plugin_parent.host
         self.profile = profile
 
-    def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
+    def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
         return [disco.DiscoFeature(NS_SEARCH)]
 
-    def getDiscoItems(self, requestor, target, nodeIdentifier=''):
+    def getDiscoItems(self, requestor, target, nodeIdentifier=""):
         return []
-
--- a/sat/plugins/plugin_xep_0059.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_xep_0059.py	Wed Jun 27 20:14:46 2018 +0200
@@ -21,6 +21,7 @@
 from sat.core.i18n import _
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 
 from wokkel import disco
@@ -38,7 +39,7 @@
     C.PI_PROTOCOLS: ["XEP-0059"],
     C.PI_MAIN: "XEP_0059",
     C.PI_HANDLER: "yes",
-    C.PI_DESCRIPTION: _("""Implementation of Result Set Management""")
+    C.PI_DESCRIPTION: _("""Implementation of Result Set Management"""),
 }
 
 
@@ -55,8 +56,8 @@
 class XEP_0059_handler(xmlstream.XMPPHandler):
     implements(iwokkel.IDisco)
 
-    def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
+    def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
         return [disco.DiscoFeature(rsm.NS_RSM)]
 
-    def getDiscoItems(self, requestor, target, nodeIdentifier=''):
+    def getDiscoItems(self, requestor, target, nodeIdentifier=""):
         return []
--- a/sat/plugins/plugin_xep_0060.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_xep_0060.py	Wed Jun 27 20:14:46 2018 +0200
@@ -20,6 +20,7 @@
 from sat.core.i18n import _
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from sat.core import exceptions
 from sat.tools import sat_defer
@@ -33,6 +34,7 @@
 import urllib
 import datetime
 from dateutil import tz
+
 # XXX: sat_tmp.wokkel.pubsub is actually use instead of wokkel version
 #      mam and rsm come from sat_tmp.wokkel too
 from wokkel import pubsub
@@ -49,70 +51,226 @@
     C.PI_RECOMMENDATIONS: ["XEP-0313"],
     C.PI_MAIN: "XEP_0060",
     C.PI_HANDLER: "yes",
-    C.PI_DESCRIPTION: _("""Implementation of PubSub Protocol""")
+    C.PI_DESCRIPTION: _("""Implementation of PubSub Protocol"""),
 }
 
 UNSPECIFIED = "unspecified error"
 MAM_FILTER = "mam_filter_"
 
 
-Extra = namedtuple('Extra', ('rsm_request', 'extra'))
+Extra = namedtuple("Extra", ("rsm_request", "extra"))
 # rsm_request is the rsm.RSMRequest build with rsm_ prefixed keys, or None
 # extra is a potentially empty dict
 
 
 class XEP_0060(object):
-    OPT_ACCESS_MODEL = 'pubsub#access_model'
-    OPT_PERSIST_ITEMS = 'pubsub#persist_items'
-    OPT_MAX_ITEMS = 'pubsub#max_items'
-    OPT_DELIVER_PAYLOADS = 'pubsub#deliver_payloads'
-    OPT_SEND_ITEM_SUBSCRIBE = 'pubsub#send_item_subscribe'
-    OPT_NODE_TYPE = 'pubsub#node_type'
-    OPT_SUBSCRIPTION_TYPE = 'pubsub#subscription_type'
-    OPT_SUBSCRIPTION_DEPTH = 'pubsub#subscription_depth'
-    OPT_ROSTER_GROUPS_ALLOWED = 'pubsub#roster_groups_allowed'
-    OPT_PUBLISH_MODEL = 'pubsub#publish_model'
-    ACCESS_OPEN = 'open'
-    ACCESS_PRESENCE = 'presence'
-    ACCESS_ROSTER = 'roster'
-    ACCESS_PUBLISHER_ROSTER = 'publisher-roster'
-    ACCESS_AUTHORIZE = 'authorize'
-    ACCESS_WHITELIST = 'whitelist'
+    OPT_ACCESS_MODEL = "pubsub#access_model"
+    OPT_PERSIST_ITEMS = "pubsub#persist_items"
+    OPT_MAX_ITEMS = "pubsub#max_items"
+    OPT_DELIVER_PAYLOADS = "pubsub#deliver_payloads"
+    OPT_SEND_ITEM_SUBSCRIBE = "pubsub#send_item_subscribe"
+    OPT_NODE_TYPE = "pubsub#node_type"
+    OPT_SUBSCRIPTION_TYPE = "pubsub#subscription_type"
+    OPT_SUBSCRIPTION_DEPTH = "pubsub#subscription_depth"
+    OPT_ROSTER_GROUPS_ALLOWED = "pubsub#roster_groups_allowed"
+    OPT_PUBLISH_MODEL = "pubsub#publish_model"
+    ACCESS_OPEN = "open"
+    ACCESS_PRESENCE = "presence"
+    ACCESS_ROSTER = "roster"
+    ACCESS_PUBLISHER_ROSTER = "publisher-roster"
+    ACCESS_AUTHORIZE = "authorize"
+    ACCESS_WHITELIST = "whitelist"
 
     def __init__(self, host):
         log.info(_(u"PubSub plugin initialization"))
         self.host = host
-        self._mam = host.plugins.get('XEP-0313')
-        self._node_cb = {} # dictionnary of callbacks for node (key: node, value: list of callbacks)
+        self._mam = host.plugins.get("XEP-0313")
+        self._node_cb = {}  # dictionnary of callbacks for node (key: node, value: list of callbacks)
         self.rt_sessions = sat_defer.RTDeferredSessions()
-        host.bridge.addMethod("psNodeCreate", ".plugin", in_sign='ssa{ss}s', out_sign='s', method=self._createNode, async=True)
-        host.bridge.addMethod("psNodeConfigurationGet", ".plugin", in_sign='sss', out_sign='a{ss}', method=self._getNodeConfiguration, async=True)
-        host.bridge.addMethod("psNodeConfigurationSet", ".plugin", in_sign='ssa{ss}s', out_sign='', method=self._setNodeConfiguration, async=True)
-        host.bridge.addMethod("psNodeAffiliationsGet", ".plugin", in_sign='sss', out_sign='a{ss}', method=self._getNodeAffiliations, async=True)
-        host.bridge.addMethod("psNodeAffiliationsSet", ".plugin", in_sign='ssa{ss}s', out_sign='', method=self._setNodeAffiliations, async=True)
-        host.bridge.addMethod("psNodeSubscriptionsGet", ".plugin", in_sign='sss', out_sign='a{ss}', method=self._getNodeSubscriptions, async=True)
-        host.bridge.addMethod("psNodeSubscriptionsSet", ".plugin", in_sign='ssa{ss}s', out_sign='', method=self._setNodeSubscriptions, async=True)
-        host.bridge.addMethod("psNodeDelete", ".plugin", in_sign='sss', out_sign='', method=self._deleteNode, async=True)
-        host.bridge.addMethod("psNodeWatchAdd", ".plugin", in_sign='sss', out_sign='', method=self._addWatch, async=False)
-        host.bridge.addMethod("psNodeWatchRemove", ".plugin", in_sign='sss', out_sign='', method=self._removeWatch, async=False)
-        host.bridge.addMethod("psAffiliationsGet", ".plugin", in_sign='sss', out_sign='a{ss}', method=self._getAffiliations, async=True)
-        host.bridge.addMethod("psItemsGet", ".plugin", in_sign='ssiassa{ss}s', out_sign='(asa{ss})', method=self._getItems, async=True)
-        host.bridge.addMethod("psItemSend", ".plugin", in_sign='ssssa{ss}s', out_sign='s', method=self._sendItem, async=True)
-        host.bridge.addMethod("psRetractItem", ".plugin", in_sign='sssbs', out_sign='', method=self._retractItem, async=True)
-        host.bridge.addMethod("psRetractItems", ".plugin", in_sign='ssasbs', out_sign='', method=self._retractItems, async=True)
-        host.bridge.addMethod("psSubscribe", ".plugin", in_sign='ssa{ss}s', out_sign='s', method=self._subscribe, async=True)
-        host.bridge.addMethod("psUnsubscribe", ".plugin", in_sign='sss', out_sign='', method=self._unsubscribe, async=True)
-        host.bridge.addMethod("psSubscriptionsGet", ".plugin", in_sign='sss', out_sign='aa{ss}', method=self._subscriptions, async=True)
-        host.bridge.addMethod("psSubscribeToMany", ".plugin", in_sign='a(ss)sa{ss}s', out_sign='s', method=self._subscribeToMany)
-        host.bridge.addMethod("psGetSubscribeRTResult", ".plugin", in_sign='ss', out_sign='(ua(sss))', method=self._manySubscribeRTResult, async=True)
-        host.bridge.addMethod("psGetFromMany", ".plugin", in_sign='a(ss)ia{ss}s', out_sign='s', method=self._getFromMany)
-        host.bridge.addMethod("psGetFromManyRTResult", ".plugin", in_sign='ss', out_sign='(ua(sssasa{ss}))', method=self._getFromManyRTResult, async=True)
+        host.bridge.addMethod(
+            "psNodeCreate",
+            ".plugin",
+            in_sign="ssa{ss}s",
+            out_sign="s",
+            method=self._createNode,
+            async=True,
+        )
+        host.bridge.addMethod(
+            "psNodeConfigurationGet",
+            ".plugin",
+            in_sign="sss",
+            out_sign="a{ss}",
+            method=self._getNodeConfiguration,
+            async=True,
+        )
+        host.bridge.addMethod(
+            "psNodeConfigurationSet",
+            ".plugin",
+            in_sign="ssa{ss}s",
+            out_sign="",
+            method=self._setNodeConfiguration,
+            async=True,
+        )
+        host.bridge.addMethod(
+            "psNodeAffiliationsGet",
+            ".plugin",
+            in_sign="sss",
+            out_sign="a{ss}",
+            method=self._getNodeAffiliations,
+            async=True,
+        )
+        host.bridge.addMethod(
+            "psNodeAffiliationsSet",
+            ".plugin",
+            in_sign="ssa{ss}s",
+            out_sign="",
+            method=self._setNodeAffiliations,
+            async=True,
+        )
+        host.bridge.addMethod(
+            "psNodeSubscriptionsGet",
+            ".plugin",
+            in_sign="sss",
+            out_sign="a{ss}",
+            method=self._getNodeSubscriptions,
+            async=True,
+        )
+        host.bridge.addMethod(
+            "psNodeSubscriptionsSet",
+            ".plugin",
+            in_sign="ssa{ss}s",
+            out_sign="",
+            method=self._setNodeSubscriptions,
+            async=True,
+        )
+        host.bridge.addMethod(
+            "psNodeDelete",
+            ".plugin",
+            in_sign="sss",
+            out_sign="",
+            method=self._deleteNode,
+            async=True,
+        )
+        host.bridge.addMethod(
+            "psNodeWatchAdd",
+            ".plugin",
+            in_sign="sss",
+            out_sign="",
+            method=self._addWatch,
+            async=False,
+        )
+        host.bridge.addMethod(
+            "psNodeWatchRemove",
+            ".plugin",
+            in_sign="sss",
+            out_sign="",
+            method=self._removeWatch,
+            async=False,
+        )
+        host.bridge.addMethod(
+            "psAffiliationsGet",
+            ".plugin",
+            in_sign="sss",
+            out_sign="a{ss}",
+            method=self._getAffiliations,
+            async=True,
+        )
+        host.bridge.addMethod(
+            "psItemsGet",
+            ".plugin",
+            in_sign="ssiassa{ss}s",
+            out_sign="(asa{ss})",
+            method=self._getItems,
+            async=True,
+        )
+        host.bridge.addMethod(
+            "psItemSend",
+            ".plugin",
+            in_sign="ssssa{ss}s",
+            out_sign="s",
+            method=self._sendItem,
+            async=True,
+        )
+        host.bridge.addMethod(
+            "psRetractItem",
+            ".plugin",
+            in_sign="sssbs",
+            out_sign="",
+            method=self._retractItem,
+            async=True,
+        )
+        host.bridge.addMethod(
+            "psRetractItems",
+            ".plugin",
+            in_sign="ssasbs",
+            out_sign="",
+            method=self._retractItems,
+            async=True,
+        )
+        host.bridge.addMethod(
+            "psSubscribe",
+            ".plugin",
+            in_sign="ssa{ss}s",
+            out_sign="s",
+            method=self._subscribe,
+            async=True,
+        )
+        host.bridge.addMethod(
+            "psUnsubscribe",
+            ".plugin",
+            in_sign="sss",
+            out_sign="",
+            method=self._unsubscribe,
+            async=True,
+        )
+        host.bridge.addMethod(
+            "psSubscriptionsGet",
+            ".plugin",
+            in_sign="sss",
+            out_sign="aa{ss}",
+            method=self._subscriptions,
+            async=True,
+        )
+        host.bridge.addMethod(
+            "psSubscribeToMany",
+            ".plugin",
+            in_sign="a(ss)sa{ss}s",
+            out_sign="s",
+            method=self._subscribeToMany,
+        )
+        host.bridge.addMethod(
+            "psGetSubscribeRTResult",
+            ".plugin",
+            in_sign="ss",
+            out_sign="(ua(sss))",
+            method=self._manySubscribeRTResult,
+            async=True,
+        )
+        host.bridge.addMethod(
+            "psGetFromMany",
+            ".plugin",
+            in_sign="a(ss)ia{ss}s",
+            out_sign="s",
+            method=self._getFromMany,
+        )
+        host.bridge.addMethod(
+            "psGetFromManyRTResult",
+            ".plugin",
+            in_sign="ss",
+            out_sign="(ua(sssasa{ss}))",
+            method=self._getFromManyRTResult,
+            async=True,
+        )
 
-        # high level observer method
-        host.bridge.addSignal("psEvent", ".plugin", signature='ssssa{ss}s')  # args: category, service(jid), node, type (C.PS_ITEMS, C.PS_DELETE), data, profile
+        #  high level observer method
+        host.bridge.addSignal(
+            "psEvent", ".plugin", signature="ssssa{ss}s"
+        )  # args: category, service(jid), node, type (C.PS_ITEMS, C.PS_DELETE), data, profile
 
         # low level observer method, used if service/node is in watching list (see psNodeWatch* methods)
-        host.bridge.addSignal("psEventRaw", ".plugin", signature='sssass')  # args: service(jid), node, type (C.PS_ITEMS, C.PS_DELETE), list of item_xml, profile
+        host.bridge.addSignal(
+            "psEventRaw", ".plugin", signature="sssass"
+        )  # args: service(jid), node, type (C.PS_ITEMS, C.PS_DELETE), list of item_xml, profile
 
     def getHandler(self, client):
         client.pubsub_client = SatPubSubClient(self.host, self)
@@ -122,10 +280,18 @@
     def profileConnected(self, client):
         client.pubsub_watching = set()
         try:
-            client.pubsub_service = jid.JID(self.host.memory.getConfig('', 'pubsub_service'))
+            client.pubsub_service = jid.JID(
+                self.host.memory.getConfig("", "pubsub_service")
+            )
         except RuntimeError:
-            log.info(_(u"Can't retrieve pubsub_service from conf, we'll use first one that we find"))
-            client.pubsub_service = yield self.host.findServiceEntity(client, "pubsub", "service")
+            log.info(
+                _(
+                    u"Can't retrieve pubsub_service from conf, we'll use first one that we find"
+                )
+            )
+            client.pubsub_service = yield self.host.findServiceEntity(
+                client, "pubsub", "service"
+            )
 
     def getFeatures(self, profile):
         try:
@@ -133,7 +299,11 @@
         except exceptions.ProfileNotSetError:
             return {}
         try:
-            return {'service': client.pubsub_service.full() if client.pubsub_service is not None else  ''}
+            return {
+                "service": client.pubsub_service.full()
+                if client.pubsub_service is not None
+                else ""
+            }
         except AttributeError:
             if self.host.isConnected(profile):
                 log.debug("Profile is not connected, service is not checked yet")
@@ -154,10 +324,10 @@
         else:
             # rsm
             rsm_args = {}
-            for arg in ('max', 'after', 'before', 'index'):
+            for arg in ("max", "after", "before", "index"):
                 try:
-                    argname = "max_" if arg == 'max' else arg
-                    rsm_args[argname] = extra.pop('rsm_{}'.format(arg))
+                    argname = "max_" if arg == "max" else arg
+                    rsm_args[argname] = extra.pop("rsm_{}".format(arg))
                 except KeyError:
                     continue
 
@@ -168,16 +338,18 @@
 
             # mam
             mam_args = {}
-            for arg in ('start', 'end'):
+            for arg in ("start", "end"):
                 try:
-                    mam_args[arg] = datetime.datetime.fromtimestamp(int(extra.pop('{}{}'.format(MAM_FILTER, arg))), tz.tzutc())
+                    mam_args[arg] = datetime.datetime.fromtimestamp(
+                        int(extra.pop("{}{}".format(MAM_FILTER, arg))), tz.tzutc()
+                    )
                 except (TypeError, ValueError):
                     log.warning(u"Bad value for {} filter".format(arg))
                 except KeyError:
                     continue
 
             try:
-                mam_args['with_jid'] = jid.JID(extra.pop('{}jid'.format(MAM_FILTER)))
+                mam_args["with_jid"] = jid.JID(extra.pop("{}jid".format(MAM_FILTER)))
             except (jid.InvalidFormat):
                 log.warning(u"Bad value for jid filter")
             except KeyError:
@@ -185,13 +357,13 @@
 
             for name, value in extra.iteritems():
                 if name.startswith(MAM_FILTER):
-                    var = name[len(MAM_FILTER):]
-                    extra_fields = mam_args.setdefault('extra_fields', [])
+                    var = name[len(MAM_FILTER) :]
+                    extra_fields = mam_args.setdefault("extra_fields", [])
                     extra_fields.append(data_form.Field(var=var, value=value))
 
             if mam_args:
-                assert 'mam' not in extra
-                extra['mam'] = mam.MAMRequest(mam.buildForm(**mam_args))
+                assert "mam" not in extra
+                extra["mam"] = mam.MAMRequest(mam.buildForm(**mam_args))
         return Extra(rsm_request, extra)
 
     def addManagedNode(self, node, **kwargs):
@@ -210,7 +382,7 @@
         for event, cb in kwargs.iteritems():
             event_name = event[:-3]
             assert event_name in C.PS_EVENTS
-            callbacks.setdefault(event_name,[]).append(cb)
+            callbacks.setdefault(event_name, []).append(cb)
 
     def removeManagedNode(self, node, *args):
         """Add a handler for a node
@@ -231,14 +403,21 @@
                     except ValueError:
                         pass
                     else:
-                        log.debug(u"removed callback {cb} for event {event} on node {node}".format(
-                            cb=callback, event=event, node=node))
+                        log.debug(
+                            u"removed callback {cb} for event {event} on node {node}".format(
+                                cb=callback, event=event, node=node
+                            )
+                        )
                         if not cb_list:
                             del registred_cb[event]
                         if not registred_cb:
                             del self._node_cb[node]
                         return
-        log.error(u"Trying to remove inexistant callback {cb} for node {node}".format(cb=callback, node=node))
+        log.error(
+            u"Trying to remove inexistant callback {cb} for node {node}".format(
+                cb=callback, node=node
+            )
+        )
 
     # def listNodes(self, service, nodeIdentifier='', profile=C.PROF_KEY_NONE):
     #     """Retrieve the name of the nodes that are accessible on the target service.
@@ -270,11 +449,21 @@
     #     d.addCallback(lambda subs: [sub.getAttribute('node') for sub in subs if sub.getAttribute('subscription') == filter_])
     #     return d
 
-    def _sendItem(self, service, nodeIdentifier, payload, item_id=None, extra=None, profile_key=C.PROF_KEY_NONE):
+    def _sendItem(
+        self,
+        service,
+        nodeIdentifier,
+        payload,
+        item_id=None,
+        extra=None,
+        profile_key=C.PROF_KEY_NONE,
+    ):
         client = self.host.getClient(profile_key)
         service = None if not service else jid.JID(service)
-        d = self.sendItem(client, service, nodeIdentifier, payload, item_id or None, extra)
-        d.addCallback(lambda ret: ret or u'')
+        d = self.sendItem(
+            client, service, nodeIdentifier, payload, item_id or None, extra
+        )
+        d.addCallback(lambda ret: ret or u"")
         return d
 
     def _getPublishedItemId(self, iq_elt, original_id):
@@ -283,12 +472,14 @@
         if not found original_id is returned, or empty string if it is None or empty string
         """
         try:
-            item_id = iq_elt.pubsub.publish.item['id']
+            item_id = iq_elt.pubsub.publish.item["id"]
         except (AttributeError, KeyError):
             item_id = None
         return item_id or original_id
 
-    def sendItem(self, client, service, nodeIdentifier, payload, item_id=None, extra=None):
+    def sendItem(
+        self, client, service, nodeIdentifier, payload, item_id=None, extra=None
+    ):
         """high level method to send one item
 
         @param service(jid.JID, None): service to send the item to
@@ -305,21 +496,40 @@
         return d
 
     def publish(self, client, service, nodeIdentifier, items=None):
-        return client.pubsub_client.publish(service, nodeIdentifier, items, client.pubsub_client.parent.jid)
+        return client.pubsub_client.publish(
+            service, nodeIdentifier, items, client.pubsub_client.parent.jid
+        )
 
     def _unwrapMAMMessage(self, message_elt):
         try:
-            item_elt = (message_elt.elements(mam.NS_MAM, 'result').next()
-                                   .elements(C.NS_FORWARD, 'forwarded').next()
-                                   .elements(C.NS_CLIENT, 'message').next()
-                                   .elements('http://jabber.org/protocol/pubsub#event', 'event').next()
-                                   .elements('http://jabber.org/protocol/pubsub#event', 'items').next()
-                                   .elements('http://jabber.org/protocol/pubsub#event', 'item').next())
+            item_elt = (
+                message_elt.elements(mam.NS_MAM, "result")
+                .next()
+                .elements(C.NS_FORWARD, "forwarded")
+                .next()
+                .elements(C.NS_CLIENT, "message")
+                .next()
+                .elements("http://jabber.org/protocol/pubsub#event", "event")
+                .next()
+                .elements("http://jabber.org/protocol/pubsub#event", "items")
+                .next()
+                .elements("http://jabber.org/protocol/pubsub#event", "item")
+                .next()
+            )
         except StopIteration:
             raise exceptions.DataError(u"Can't find Item in MAM message element")
         return item_elt
 
-    def _getItems(self, service='', node='', max_items=10, item_ids=None, sub_id=None, extra_dict=None, profile_key=C.PROF_KEY_NONE):
+    def _getItems(
+        self,
+        service="",
+        node="",
+        max_items=10,
+        item_ids=None,
+        sub_id=None,
+        extra_dict=None,
+        profile_key=C.PROF_KEY_NONE,
+    ):
         """Get items from pubsub node
 
         @param max_items(int): maximum number of item to get, C.NO_LIMIT for no limit
@@ -328,11 +538,30 @@
         service = jid.JID(service) if service else None
         max_items = None if max_items == C.NO_LIMIT else max_items
         extra = self.parseExtra(extra_dict)
-        d = self.getItems(client, service, node or None, max_items or None, item_ids, sub_id or None, extra.rsm_request, extra.extra)
+        d = self.getItems(
+            client,
+            service,
+            node or None,
+            max_items or None,
+            item_ids,
+            sub_id or None,
+            extra.rsm_request,
+            extra.extra,
+        )
         d.addCallback(self.serItemsData)
         return d
 
-    def getItems(self, client, service, node, max_items=None, item_ids=None, sub_id=None, rsm_request=None, extra=None):
+    def getItems(
+        self,
+        client,
+        service,
+        node,
+        max_items=None,
+        item_ids=None,
+        sub_id=None,
+        rsm_request=None,
+        extra=None,
+    ):
         """Retrieve pubsub items from a node.
 
         @param service (JID, None): pubsub service.
@@ -354,9 +583,11 @@
         if extra is None:
             extra = {}
         try:
-            mam_query = extra['mam']
+            mam_query = extra["mam"]
         except KeyError:
-            d = client.pubsub_client.items(service, node, max_items, item_ids, sub_id, None, rsm_request)
+            d = client.pubsub_client.items(
+                service, node, max_items, item_ids, sub_id, None, rsm_request
+            )
         else:
             # if mam is requested, we have to do a totally different query
             if self._mam is None:
@@ -368,25 +599,35 @@
             if mam_query.node is None:
                 mam_query.node = node
             elif mam_query.node != node:
-                raise exceptions.DataError(u"MAM query node is incoherent with getItems's node")
+                raise exceptions.DataError(
+                    u"MAM query node is incoherent with getItems's node"
+                )
             if mam_query.rsm is None:
                 mam_query.rsm = rsm_request
             else:
                 if mam_query.rsm != rsm_request:
-                    raise exceptions.DataError(u"Conflict between RSM request and MAM's RSM request")
+                    raise exceptions.DataError(
+                        u"Conflict between RSM request and MAM's RSM request"
+                    )
             d = self._mam.getArchives(client, mam_query, service, self._unwrapMAMMessage)
 
         try:
-            subscribe = C.bool(extra['subscribe'])
+            subscribe = C.bool(extra["subscribe"])
         except KeyError:
             subscribe = False
 
         def subscribeEb(failure, service, node):
             failure.trap(error.StanzaError)
-            log.warning(u"Could not subscribe to node {} on service {}: {}".format(node, unicode(service), unicode(failure.value)))
+            log.warning(
+                u"Could not subscribe to node {} on service {}: {}".format(
+                    node, unicode(service), unicode(failure.value)
+                )
+            )
 
         def doSubscribe(items):
-            self.subscribe(service, node, profile_key=client.profile).addErrback(subscribeEb, service, node)
+            self.subscribe(service, node, profile_key=client.profile).addErrback(
+                subscribeEb, service, node
+            )
             return items
 
         if subscribe:
@@ -395,12 +636,18 @@
         def addMetadata(result):
             items, rsm_response = result
             service_jid = service if service else client.jid.userhostJID()
-            metadata = {'service': service_jid,
-                        'node': node,
-                        'uri': self.getNodeURI(service_jid, node),
-                       }
+            metadata = {
+                "service": service_jid,
+                "node": node,
+                "uri": self.getNodeURI(service_jid, node),
+            }
             if rsm_request is not None and rsm_response is not None:
-                metadata.update({'rsm_{}'.format(key): value for key, value in rsm_response.toDict().iteritems()})
+                metadata.update(
+                    {
+                        "rsm_{}".format(key): value
+                        for key, value in rsm_response.toDict().iteritems()
+                    }
+                )
             return (items, metadata)
 
         d.addCallback(addMetadata)
@@ -431,17 +678,38 @@
     #         d_dict[publisher] = self.getItems(service, node, max_items, None, sub_id, rsm, client.profile)
     #     defer.returnValue(d_dict)
 
-    def getOptions(self, service, nodeIdentifier, subscriber, subscriptionIdentifier=None, profile_key=C.PROF_KEY_NONE):
+    def getOptions(
+        self,
+        service,
+        nodeIdentifier,
+        subscriber,
+        subscriptionIdentifier=None,
+        profile_key=C.PROF_KEY_NONE,
+    ):
         client = self.host.getClient(profile_key)
-        return client.pubsub_client.getOptions(service, nodeIdentifier, subscriber, subscriptionIdentifier)
+        return client.pubsub_client.getOptions(
+            service, nodeIdentifier, subscriber, subscriptionIdentifier
+        )
 
-    def setOptions(self, service, nodeIdentifier, subscriber, options, subscriptionIdentifier=None, profile_key=C.PROF_KEY_NONE):
+    def setOptions(
+        self,
+        service,
+        nodeIdentifier,
+        subscriber,
+        options,
+        subscriptionIdentifier=None,
+        profile_key=C.PROF_KEY_NONE,
+    ):
         client = self.host.getClient(profile_key)
-        return client.pubsub_client.setOptions(service, nodeIdentifier, subscriber, options, subscriptionIdentifier)
+        return client.pubsub_client.setOptions(
+            service, nodeIdentifier, subscriber, options, subscriptionIdentifier
+        )
 
     def _createNode(self, service_s, nodeIdentifier, options, profile_key):
         client = self.host.getClient(profile_key)
-        return self.createNode(client, jid.JID(service_s) if service_s else None, nodeIdentifier, options)
+        return self.createNode(
+            client, jid.JID(service_s) if service_s else None, nodeIdentifier, options
+        )
 
     def createNode(self, client, service, nodeIdentifier=None, options=None):
         """Create a new node
@@ -461,28 +729,31 @@
         try:
             yield self.createNode(client, service, nodeIdentifier, options)
         except error.StanzaError as e:
-            if e.condition == 'conflict':
+            if e.condition == "conflict":
                 pass
             else:
                 raise e
 
     def _getNodeConfiguration(self, service_s, nodeIdentifier, profile_key):
         client = self.host.getClient(profile_key)
-        d = self.getConfiguration(client, jid.JID(service_s) if service_s else None, nodeIdentifier)
+        d = self.getConfiguration(
+            client, jid.JID(service_s) if service_s else None, nodeIdentifier
+        )
+
         def serialize(form):
             # FIXME: better more generic dataform serialisation should be available in SàT
             return {f.var: unicode(f.value) for f in form.fields.values()}
+
         d.addCallback(serialize)
         return d
 
     def getConfiguration(self, client, service, nodeIdentifier):
-        request = pubsub.PubSubRequest('configureGet')
+        request = pubsub.PubSubRequest("configureGet")
         request.recipient = service
         request.nodeIdentifier = nodeIdentifier
 
         def cb(iq):
-            form = data_form.findForm(iq.pubsub.configure,
-                                      pubsub.NS_PUBSUB_NODE_CONFIG)
+            form = data_form.findForm(iq.pubsub.configure, pubsub.NS_PUBSUB_NODE_CONFIG)
             form.typeCheck()
             return form
 
@@ -492,16 +763,19 @@
 
     def _setNodeConfiguration(self, service_s, nodeIdentifier, options, profile_key):
         client = self.host.getClient(profile_key)
-        d = self.setConfiguration(client, jid.JID(service_s) if service_s else None, nodeIdentifier, options)
+        d = self.setConfiguration(
+            client, jid.JID(service_s) if service_s else None, nodeIdentifier, options
+        )
         return d
 
     def setConfiguration(self, client, service, nodeIdentifier, options):
-        request = pubsub.PubSubRequest('configureSet')
+        request = pubsub.PubSubRequest("configureSet")
         request.recipient = service
         request.nodeIdentifier = nodeIdentifier
 
-        form = data_form.Form(formType='submit',
-                              formNamespace=pubsub.NS_PUBSUB_NODE_CONFIG)
+        form = data_form.Form(
+            formType="submit", formNamespace=pubsub.NS_PUBSUB_NODE_CONFIG
+        )
         form.makeFields(options)
         request.options = form
 
@@ -510,7 +784,9 @@
 
     def _getAffiliations(self, service_s, nodeIdentifier, profile_key):
         client = self.host.getClient(profile_key)
-        d = self.getAffiliations(client, jid.JID(service_s) if service_s else None, nodeIdentifier or None)
+        d = self.getAffiliations(
+            client, jid.JID(service_s) if service_s else None, nodeIdentifier or None
+        )
         return d
 
     def getAffiliations(self, client, service, nodeIdentifier=None):
@@ -519,19 +795,32 @@
         @param nodeIdentifier(unicode, None): node to get affiliation from
             None to get all nodes affiliations for this service
         """
-        request = pubsub.PubSubRequest('affiliations')
+        request = pubsub.PubSubRequest("affiliations")
         request.recipient = service
         request.nodeIdentifier = nodeIdentifier
 
         def cb(iq_elt):
             try:
-                affiliations_elt = next(iq_elt.pubsub.elements((pubsub.NS_PUBSUB, 'affiliations')))
+                affiliations_elt = next(
+                    iq_elt.pubsub.elements((pubsub.NS_PUBSUB, "affiliations"))
+                )
             except StopIteration:
-                raise ValueError(_(u"Invalid result: missing <affiliations> element: {}").format(iq_elt.toXml))
+                raise ValueError(
+                    _(u"Invalid result: missing <affiliations> element: {}").format(
+                        iq_elt.toXml
+                    )
+                )
             try:
-                return {e['node']: e['affiliation'] for e in affiliations_elt.elements((pubsub.NS_PUBSUB, 'affiliation'))}
+                return {
+                    e["node"]: e["affiliation"]
+                    for e in affiliations_elt.elements((pubsub.NS_PUBSUB, "affiliation"))
+                }
             except KeyError:
-                raise ValueError(_(u"Invalid result: bad <affiliation> element: {}").format(iq_elt.toXml))
+                raise ValueError(
+                    _(u"Invalid result: bad <affiliation> element: {}").format(
+                        iq_elt.toXml
+                    )
+                )
 
         d = request.send(client.xmlstream)
         d.addCallback(cb)
@@ -539,34 +828,62 @@
 
     def _getNodeAffiliations(self, service_s, nodeIdentifier, profile_key):
         client = self.host.getClient(profile_key)
-        d = self.getNodeAffiliations(client, jid.JID(service_s) if service_s else None, nodeIdentifier)
-        d.addCallback(lambda affiliations: {j.full(): a for j, a in affiliations.iteritems()})
+        d = self.getNodeAffiliations(
+            client, jid.JID(service_s) if service_s else None, nodeIdentifier
+        )
+        d.addCallback(
+            lambda affiliations: {j.full(): a for j, a in affiliations.iteritems()}
+        )
         return d
 
     def getNodeAffiliations(self, client, service, nodeIdentifier):
         """Retrieve affiliations of a node owned by profile"""
-        request = pubsub.PubSubRequest('affiliationsGet')
+        request = pubsub.PubSubRequest("affiliationsGet")
         request.recipient = service
         request.nodeIdentifier = nodeIdentifier
 
         def cb(iq_elt):
             try:
-                affiliations_elt = next(iq_elt.pubsub.elements((pubsub.NS_PUBSUB_OWNER, 'affiliations')))
+                affiliations_elt = next(
+                    iq_elt.pubsub.elements((pubsub.NS_PUBSUB_OWNER, "affiliations"))
+                )
             except StopIteration:
-                raise ValueError(_(u"Invalid result: missing <affiliations> element: {}").format(iq_elt.toXml))
+                raise ValueError(
+                    _(u"Invalid result: missing <affiliations> element: {}").format(
+                        iq_elt.toXml
+                    )
+                )
             try:
-                return {jid.JID(e['jid']): e['affiliation'] for e in affiliations_elt.elements((pubsub.NS_PUBSUB_OWNER, 'affiliation'))}
+                return {
+                    jid.JID(e["jid"]): e["affiliation"]
+                    for e in affiliations_elt.elements(
+                        (pubsub.NS_PUBSUB_OWNER, "affiliation")
+                    )
+                }
             except KeyError:
-                raise ValueError(_(u"Invalid result: bad <affiliation> element: {}").format(iq_elt.toXml))
+                raise ValueError(
+                    _(u"Invalid result: bad <affiliation> element: {}").format(
+                        iq_elt.toXml
+                    )
+                )
 
         d = request.send(client.xmlstream)
         d.addCallback(cb)
         return d
 
-    def _setNodeAffiliations(self, service_s, nodeIdentifier, affiliations, profile_key=C.PROF_KEY_NONE):
+    def _setNodeAffiliations(
+        self, service_s, nodeIdentifier, affiliations, profile_key=C.PROF_KEY_NONE
+    ):
         client = self.host.getClient(profile_key)
-        affiliations = {jid.JID(jid_): affiliation for jid_, affiliation in affiliations.iteritems()}
-        d = self.setNodeAffiliations(client, jid.JID(service_s) if service_s else None, nodeIdentifier, affiliations)
+        affiliations = {
+            jid.JID(jid_): affiliation for jid_, affiliation in affiliations.iteritems()
+        }
+        d = self.setNodeAffiliations(
+            client,
+            jid.JID(service_s) if service_s else None,
+            nodeIdentifier,
+            affiliations,
+        )
         return d
 
     def setNodeAffiliations(self, client, service, nodeIdentifier, affiliations):
@@ -575,7 +892,7 @@
         @param affiliations(dict[jid.JID, unicode]): affiliations to set
             check https://xmpp.org/extensions/xep-0060.html#affiliations for a list of possible affiliations
         """
-        request = pubsub.PubSubRequest('affiliationsSet')
+        request = pubsub.PubSubRequest("affiliationsSet")
         request.recipient = service
         request.nodeIdentifier = nodeIdentifier
         request.affiliations = affiliations
@@ -584,7 +901,9 @@
 
     def _deleteNode(self, service_s, nodeIdentifier, profile_key):
         client = self.host.getClient(profile_key)
-        return self.deleteNode(client, jid.JID(service_s) if service_s else None, nodeIdentifier)
+        return self.deleteNode(
+            client, jid.JID(service_s) if service_s else None, nodeIdentifier
+        )
 
     def deleteNode(self, client, service, nodeIdentifier):
         return client.pubsub_client.deleteNode(service, nodeIdentifier)
@@ -607,48 +926,86 @@
         service = jid.JID(service_s) if service_s else client.jid.userhostJID()
         client.pubsub_watching.remove((service, node))
 
-    def _retractItem(self, service_s, nodeIdentifier, itemIdentifier, notify, profile_key):
-        return self._retractItems(service_s, nodeIdentifier, (itemIdentifier,), notify, profile_key)
+    def _retractItem(
+        self, service_s, nodeIdentifier, itemIdentifier, notify, profile_key
+    ):
+        return self._retractItems(
+            service_s, nodeIdentifier, (itemIdentifier,), notify, profile_key
+        )
 
-    def _retractItems(self, service_s, nodeIdentifier, itemIdentifiers, notify, profile_key):
-        return self.retractItems(jid.JID(service_s) if service_s else None, nodeIdentifier, itemIdentifiers, notify, profile_key)
+    def _retractItems(
+        self, service_s, nodeIdentifier, itemIdentifiers, notify, profile_key
+    ):
+        return self.retractItems(
+            jid.JID(service_s) if service_s else None,
+            nodeIdentifier,
+            itemIdentifiers,
+            notify,
+            profile_key,
+        )
 
-    def retractItems(self, service, nodeIdentifier, itemIdentifiers, notify=True, profile_key=C.PROF_KEY_NONE):
+    def retractItems(
+        self,
+        service,
+        nodeIdentifier,
+        itemIdentifiers,
+        notify=True,
+        profile_key=C.PROF_KEY_NONE,
+    ):
         client = self.host.getClient(profile_key)
-        return client.pubsub_client.retractItems(service, nodeIdentifier, itemIdentifiers, notify=True)
+        return client.pubsub_client.retractItems(
+            service, nodeIdentifier, itemIdentifiers, notify=True
+        )
 
     def _subscribe(self, service, nodeIdentifier, options, profile_key=C.PROF_KEY_NONE):
         client = self.host.getClient(profile_key)
         service = None if not service else jid.JID(service)
         d = self.subscribe(client, service, nodeIdentifier, options=options or None)
-        d.addCallback(lambda subscription: subscription.subscriptionIdentifier or u'')
+        d.addCallback(lambda subscription: subscription.subscriptionIdentifier or u"")
         return d
 
     def subscribe(self, client, service, nodeIdentifier, sub_jid=None, options=None):
         # TODO: reimplement a subscribtion cache, checking that we have not subscription before trying to subscribe
-        return client.pubsub_client.subscribe(service, nodeIdentifier, sub_jid or client.jid.userhostJID(), options=options)
+        return client.pubsub_client.subscribe(
+            service, nodeIdentifier, sub_jid or client.jid.userhostJID(), options=options
+        )
 
     def _unsubscribe(self, service, nodeIdentifier, profile_key=C.PROF_KEY_NONE):
         client = self.host.getClient(profile_key)
         service = None if not service else jid.JID(service)
         return self.unsubscribe(client, service, nodeIdentifier)
 
-    def unsubscribe(self, client, service, nodeIdentifier, sub_jid=None, subscriptionIdentifier=None, sender=None):
-        return client.pubsub_client.unsubscribe(service, nodeIdentifier, sub_jid or client.jid.userhostJID(), subscriptionIdentifier, sender)
+    def unsubscribe(
+        self,
+        client,
+        service,
+        nodeIdentifier,
+        sub_jid=None,
+        subscriptionIdentifier=None,
+        sender=None,
+    ):
+        return client.pubsub_client.unsubscribe(
+            service,
+            nodeIdentifier,
+            sub_jid or client.jid.userhostJID(),
+            subscriptionIdentifier,
+            sender,
+        )
 
-    def _subscriptions(self, service, nodeIdentifier='', profile_key=C.PROF_KEY_NONE):
+    def _subscriptions(self, service, nodeIdentifier="", profile_key=C.PROF_KEY_NONE):
         client = self.host.getClient(profile_key)
         service = None if not service else jid.JID(service)
 
         def gotSubscriptions(subscriptions):
             # we replace pubsub.Subscription instance by dict that we can serialize
             for idx, sub in enumerate(subscriptions):
-                sub_dict = {'node': sub.nodeIdentifier,
-                            'subscriber': sub.subscriber.full(),
-                            'state': sub.state
-                           }
+                sub_dict = {
+                    "node": sub.nodeIdentifier,
+                    "subscriber": sub.subscriber.full(),
+                    "state": sub.state,
+                }
                 if sub.subscriptionIdentifier is not None:
-                    sub_dict['id'] = sub.subscriptionIdentifier
+                    sub_dict["id"] = sub.subscriptionIdentifier
                 subscriptions[idx] = sub_dict
 
             return subscriptions
@@ -679,19 +1036,20 @@
         # XXX: urllib.urlencode use "&" to separate value, while XMPP URL (cf. RFC 5122)
         #      use ";" as a separator. So if more than one value is used in query_data,
         #      urlencode MUST NOT BE USED.
-        query_data = [('node', node.encode('utf-8'))]
+        query_data = [("node", node.encode("utf-8"))]
         if item is not None:
-            query_data.append(('item', item.encode('utf-8')))
+            query_data.append(("item", item.encode("utf-8")))
         return "xmpp:{service}?;{query}".format(
-            service=service.userhost(),
-            query=urllib.urlencode(query_data)
-            ).decode('utf-8')
+            service=service.userhost(), query=urllib.urlencode(query_data)
+        ).decode("utf-8")
 
     ## methods to manage several stanzas/jids at once ##
 
     # generic #
 
-    def getRTResults(self, session_id, on_success=None, on_error=None, profile=C.PROF_KEY_NONE):
+    def getRTResults(
+        self, session_id, on_success=None, on_error=None, profile=C.PROF_KEY_NONE
+    ):
         return self.rt_sessions.getResults(session_id, on_success, on_error, profile)
 
     def serItemsData(self, items_data, item_cb=lambda item: item.toXml()):
@@ -705,7 +1063,10 @@
         @return (tuple): a serialised form ready to go throught bridge
         """
         items, metadata = items_data
-        return [item_cb(item) for item in items], {key: unicode(value) for key, value in metadata.iteritems()}
+        return (
+            [item_cb(item) for item in items],
+            {key: unicode(value) for key, value in metadata.iteritems()},
+        )
 
     def serItemsDataD(self, items_data, item_cb):
         """Helper method to serialise result from [getItems], deferred version
@@ -719,11 +1080,20 @@
         @return (tuple): a deferred which fire a serialised form ready to go throught bridge
         """
         items, metadata = items_data
+
         def eb(failure):
-            log.warning("Error while serialising/parsing item: {}".format(unicode(failure.value)))
+            log.warning(
+                "Error while serialising/parsing item: {}".format(unicode(failure.value))
+            )
+
         d = defer.gatherResults([item_cb(item).addErrback(eb) for item in items])
+
         def finishSerialisation(serialised_items):
-            return [item for item in serialised_items if item is not None], {key: unicode(value) for key, value in metadata.iteritems()}
+            return (
+                [item for item in serialised_items if item is not None],
+                {key: unicode(value) for key, value in metadata.iteritems()},
+            )
+
         d.addCallback(finishSerialisation)
         return d
 
@@ -739,14 +1109,23 @@
         """
         if failure_result is None:
             failure_result = ()
-        return [('', result) if success else (unicode(result.result) or UNSPECIFIED, failure_result) for success, result in results]
+        return [
+            ("", result)
+            if success
+            else (unicode(result.result) or UNSPECIFIED, failure_result)
+            for success, result in results
+        ]
 
     # subscribe #
 
     def _getNodeSubscriptions(self, service_s, nodeIdentifier, profile_key):
         client = self.host.getClient(profile_key)
-        d = self.getNodeSubscriptions(client, jid.JID(service_s) if service_s else None, nodeIdentifier)
-        d.addCallback(lambda subscriptions: {j.full(): a for j, a in subscriptions.iteritems()})
+        d = self.getNodeSubscriptions(
+            client, jid.JID(service_s) if service_s else None, nodeIdentifier
+        )
+        d.addCallback(
+            lambda subscriptions: {j.full(): a for j, a in subscriptions.iteritems()}
+        )
         return d
 
     def getNodeSubscriptions(self, client, service, nodeIdentifier):
@@ -756,30 +1135,55 @@
         """
         if not nodeIdentifier:
             raise exceptions.DataError("node identifier can't be empty")
-        request = pubsub.PubSubRequest('subscriptionsGet')
+        request = pubsub.PubSubRequest("subscriptionsGet")
         request.recipient = service
         request.nodeIdentifier = nodeIdentifier
 
         def cb(iq_elt):
             try:
-                subscriptions_elt = next(iq_elt.pubsub.elements((pubsub.NS_PUBSUB, 'subscriptions')))
+                subscriptions_elt = next(
+                    iq_elt.pubsub.elements((pubsub.NS_PUBSUB, "subscriptions"))
+                )
             except StopIteration:
-                raise ValueError(_(u"Invalid result: missing <subscriptions> element: {}").format(iq_elt.toXml))
+                raise ValueError(
+                    _(u"Invalid result: missing <subscriptions> element: {}").format(
+                        iq_elt.toXml
+                    )
+                )
             except AttributeError as e:
                 raise ValueError(_(u"Invalid result: {}").format(e))
             try:
-                return {jid.JID(s['jid']): s['subscription'] for s in subscriptions_elt.elements((pubsub.NS_PUBSUB, 'subscription'))}
+                return {
+                    jid.JID(s["jid"]): s["subscription"]
+                    for s in subscriptions_elt.elements(
+                        (pubsub.NS_PUBSUB, "subscription")
+                    )
+                }
             except KeyError:
-                raise ValueError(_(u"Invalid result: bad <subscription> element: {}").format(iq_elt.toXml))
+                raise ValueError(
+                    _(u"Invalid result: bad <subscription> element: {}").format(
+                        iq_elt.toXml
+                    )
+                )
 
         d = request.send(client.xmlstream)
         d.addCallback(cb)
         return d
 
-    def _setNodeSubscriptions(self, service_s, nodeIdentifier, subscriptions, profile_key=C.PROF_KEY_NONE):
+    def _setNodeSubscriptions(
+        self, service_s, nodeIdentifier, subscriptions, profile_key=C.PROF_KEY_NONE
+    ):
         client = self.host.getClient(profile_key)
-        subscriptions = {jid.JID(jid_): subscription for jid_, subscription in subscriptions.iteritems()}
-        d = self.setNodeSubscriptions(client, jid.JID(service_s) if service_s else None, nodeIdentifier, subscriptions)
+        subscriptions = {
+            jid.JID(jid_): subscription
+            for jid_, subscription in subscriptions.iteritems()
+        }
+        d = self.setNodeSubscriptions(
+            client,
+            jid.JID(service_s) if service_s else None,
+            nodeIdentifier,
+            subscriptions,
+        )
         return d
 
     def setNodeSubscriptions(self, client, service, nodeIdentifier, subscriptions):
@@ -788,10 +1192,13 @@
         @param subscriptions(dict[jid.JID, unicode]): subscriptions to set
             check https://xmpp.org/extensions/xep-0060.html#substates for a list of possible subscriptions
         """
-        request = pubsub.PubSubRequest('subscriptionsSet')
+        request = pubsub.PubSubRequest("subscriptionsSet")
         request.recipient = service
         request.nodeIdentifier = nodeIdentifier
-        request.subscriptions = {pubsub.Subscription(nodeIdentifier, jid_, state) for jid_, state in subscriptions.iteritems()}
+        request.subscriptions = {
+            pubsub.Subscription(nodeIdentifier, jid_, state)
+            for jid_, state in subscriptions.iteritems()
+        }
         d = request.send(client.xmlstream)
         return d
 
@@ -808,16 +1215,37 @@
         @param profile_key: %(doc_profile_key)s
         """
         profile = self.host.getClient(profile_key).profile
-        d = self.rt_sessions.getResults(session_id, on_success=lambda result:'', on_error=lambda failure:unicode(failure.value), profile=profile)
+        d = self.rt_sessions.getResults(
+            session_id,
+            on_success=lambda result: "",
+            on_error=lambda failure: unicode(failure.value),
+            profile=profile,
+        )
         # we need to convert jid.JID to unicode with full() to serialise it for the bridge
-        d.addCallback(lambda ret: (ret[0], [(service.full(), node, '' if success else failure or UNSPECIFIED)
-                                            for (service, node), (success, failure) in ret[1].iteritems()]))
+        d.addCallback(
+            lambda ret: (
+                ret[0],
+                [
+                    (service.full(), node, "" if success else failure or UNSPECIFIED)
+                    for (service, node), (success, failure) in ret[1].iteritems()
+                ],
+            )
+        )
         return d
 
-    def _subscribeToMany(self, node_data, subscriber=None, options=None, profile_key=C.PROF_KEY_NONE):
-        return self.subscribeToMany([(jid.JID(service), unicode(node)) for service, node in node_data], jid.JID(subscriber), options, profile_key)
+    def _subscribeToMany(
+        self, node_data, subscriber=None, options=None, profile_key=C.PROF_KEY_NONE
+    ):
+        return self.subscribeToMany(
+            [(jid.JID(service), unicode(node)) for service, node in node_data],
+            jid.JID(subscriber),
+            options,
+            profile_key,
+        )
 
-    def subscribeToMany(self, node_data, subscriber, options=None, profile_key=C.PROF_KEY_NONE):
+    def subscribeToMany(
+        self, node_data, subscriber, options=None, profile_key=C.PROF_KEY_NONE
+    ):
         """Subscribe to several nodes at once.
 
         @param node_data (iterable[tuple]): iterable of tuple (service, node) where:
@@ -831,7 +1259,9 @@
         client = self.host.getClient(profile_key)
         deferreds = {}
         for service, node in node_data:
-            deferreds[(service, node)] = client.pubsub_client.subscribe(service, node, subscriber, options=options)
+            deferreds[(service, node)] = client.pubsub_client.subscribe(
+                service, node, subscriber, options=options
+            )
         return self.rt_sessions.newSession(deferreds, client.profile)
         # found_nodes = yield self.listNodes(service, profile=client.profile)
         # subscribed_nodes = yield self.listSubscribedNodes(service, profile=client.profile)
@@ -860,24 +1290,49 @@
                 - metadata(dict): serialised metadata
         """
         profile = self.host.getClient(profile_key).profile
-        d = self.rt_sessions.getResults(session_id,
-                                        on_success=lambda result: ('', self.serItemsData(result)),
-                                        on_error=lambda failure: (unicode(failure.value) or UNSPECIFIED, ([],{})),
-                                        profile=profile)
-        d.addCallback(lambda ret: (ret[0],
-                                   [(service.full(), node, failure, items, metadata)
-                                    for (service, node), (success, (failure, (items, metadata))) in ret[1].iteritems()]))
+        d = self.rt_sessions.getResults(
+            session_id,
+            on_success=lambda result: ("", self.serItemsData(result)),
+            on_error=lambda failure: (unicode(failure.value) or UNSPECIFIED, ([], {})),
+            profile=profile,
+        )
+        d.addCallback(
+            lambda ret: (
+                ret[0],
+                [
+                    (service.full(), node, failure, items, metadata)
+                    for (service, node), (success, (failure, (items, metadata))) in ret[
+                        1
+                    ].iteritems()
+                ],
+            )
+        )
         return d
 
-    def _getFromMany(self, node_data, max_item=10, extra_dict=None, profile_key=C.PROF_KEY_NONE):
+    def _getFromMany(
+        self, node_data, max_item=10, extra_dict=None, profile_key=C.PROF_KEY_NONE
+    ):
         """
         @param max_item(int): maximum number of item to get, C.NO_LIMIT for no limit
         """
         max_item = None if max_item == C.NO_LIMIT else max_item
         extra = self.parseExtra(extra_dict)
-        return self.getFromMany([(jid.JID(service), unicode(node)) for service, node in node_data], max_item, extra.rsm_request, extra.extra, profile_key)
+        return self.getFromMany(
+            [(jid.JID(service), unicode(node)) for service, node in node_data],
+            max_item,
+            extra.rsm_request,
+            extra.extra,
+            profile_key,
+        )
 
-    def getFromMany(self, node_data, max_item=None, rsm_request=None, extra=None, profile_key=C.PROF_KEY_NONE):
+    def getFromMany(
+        self,
+        node_data,
+        max_item=None,
+        rsm_request=None,
+        extra=None,
+        profile_key=C.PROF_KEY_NONE,
+    ):
         """Get items from many nodes at once
 
         @param node_data (iterable[tuple]): iterable of tuple (service, node) where:
@@ -891,7 +1346,9 @@
         client = self.host.getClient(profile_key)
         deferreds = {}
         for service, node in node_data:
-            deferreds[(service, node)] = self.getItems(client, service, node, max_item, rsm_request=rsm_request, extra=extra)
+            deferreds[(service, node)] = self.getItems(
+                client, service, node, max_item, rsm_request=rsm_request, extra=extra
+            )
         return self.rt_sessions.newSession(deferreds, client.profile)
 
 
@@ -930,7 +1387,13 @@
         client = self.parent
         if (event.sender, event.nodeIdentifier) in client.pubsub_watching:
             raw_items = [i.toXml() for i in event.items]
-            self.host.bridge.psEventRaw(event.sender.full(), event.nodeIdentifier, C.PS_ITEMS, raw_items, client.profile)
+            self.host.bridge.psEventRaw(
+                event.sender.full(),
+                event.nodeIdentifier,
+                C.PS_ITEMS,
+                raw_items,
+                client.profile,
+            )
 
     def deleteReceived(self, event):
         log.debug((u"Publish node deleted"))
@@ -938,7 +1401,9 @@
             callback(self.parent, event)
         client = self.parent
         if (event.sender, event.nodeIdentifier) in client.pubsub_watching:
-            self.host.bridge.psEventRaw(event.sender.full(), event.nodeIdentifier, C.PS_DELETE, [], client.profile)
+            self.host.bridge.psEventRaw(
+                event.sender.full(), event.nodeIdentifier, C.PS_DELETE, [], client.profile
+            )
 
     def subscriptions(self, service, nodeIdentifier, sender=None):
         """Return the list of subscriptions to the given service and node.
@@ -949,7 +1414,7 @@
         @type nodeIdentifier: C{unicode}
         @return (list[pubsub.Subscription]): list of subscriptions
         """
-        request = pubsub.PubSubRequest('subscriptions')
+        request = pubsub.PubSubRequest("subscriptions")
         request.recipient = service
         request.nodeIdentifier = nodeIdentifier
         request.sender = sender
@@ -957,20 +1422,24 @@
 
         def cb(iq):
             subs = []
-            for subscription_elt in iq.pubsub.subscriptions.elements(pubsub.NS_PUBSUB, 'subscription'):
-                subscription = pubsub.Subscription(subscription_elt['node'],
-                                                   jid.JID(subscription_elt['jid']),
-                                                   subscription_elt['subscription'],
-                                                   subscriptionIdentifier=subscription_elt.getAttribute('subid'))
+            for subscription_elt in iq.pubsub.subscriptions.elements(
+                pubsub.NS_PUBSUB, "subscription"
+            ):
+                subscription = pubsub.Subscription(
+                    subscription_elt["node"],
+                    jid.JID(subscription_elt["jid"]),
+                    subscription_elt["subscription"],
+                    subscriptionIdentifier=subscription_elt.getAttribute("subid"),
+                )
                 subs.append(subscription)
             return subs
 
         return d.addCallback(cb)
 
-    def getDiscoInfo(self, requestor, service, nodeIdentifier=''):
+    def getDiscoInfo(self, requestor, service, nodeIdentifier=""):
         disco_info = []
         self.host.trigger.point("PubSub Disco Info", disco_info, self.parent.profile)
         return disco_info
 
-    def getDiscoItems(self, requestor, service, nodeIdentifier=''):
+    def getDiscoItems(self, requestor, service, nodeIdentifier=""):
         return []
--- a/sat/plugins/plugin_xep_0065.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_xep_0065.py	Wed Jun 27 20:14:46 2018 +0200
@@ -1,5 +1,5 @@
 #!/usr/bin/env python2
-#-*- coding: utf-8 -*-
+# -*- coding: utf-8 -*-
 
 # SAT plugin for managing xep-0065
 
@@ -56,6 +56,7 @@
 
 from sat.core.i18n import _
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from sat.core.constants import Const as C
 from sat.core import exceptions
@@ -92,25 +93,27 @@
     C.PI_RECOMMENDATIONS: ["NAT-PORT"],
     C.PI_MAIN: "XEP_0065",
     C.PI_HANDLER: "yes",
-    C.PI_DESCRIPTION: _("""Implementation of SOCKS5 Bytestreams""")
+    C.PI_DESCRIPTION: _("""Implementation of SOCKS5 Bytestreams"""),
 }
 
 IQ_SET = '/iq[@type="set"]'
-NS_BS = 'http://jabber.org/protocol/bytestreams'
+NS_BS = "http://jabber.org/protocol/bytestreams"
 BS_REQUEST = IQ_SET + '/query[@xmlns="' + NS_BS + '"]'
-TIMER_KEY = 'timer'
-DEFER_KEY = 'finished' # key of the deferred used to track session end
-SERVER_STARTING_PORT = 0 # starting number for server port search (0 to ask automatic attribution)
+TIMER_KEY = "timer"
+DEFER_KEY = "finished"  # key of the deferred used to track session end
+SERVER_STARTING_PORT = (
+    0
+)  # starting number for server port search (0 to ask automatic attribution)
 
 # priorities are candidates local priorities, must be a int between 0 and 65535
 PRIORITY_BEST_DIRECT = 10000
 PRIORITY_DIRECT = 5000
 PRIORITY_ASSISTED = 1000
-PRIORITY_PROXY = 0.2 # proxy is the last option for s5b
-CANDIDATE_DELAY = 0.2 # see XEP-0260 §4
-CANDIDATE_DELAY_PROXY = 0.2 # additional time for proxy types (see XEP-0260 §4 note 3)
+PRIORITY_PROXY = 0.2  # proxy is the last option for s5b
+CANDIDATE_DELAY = 0.2  # see XEP-0260 §4
+CANDIDATE_DELAY_PROXY = 0.2  # additional time for proxy types (see XEP-0260 §4 note 3)
 
-TIMEOUT = 300 # maxium time between session creation and stream start
+TIMEOUT = 300  # maxium time between session creation and stream start
 
 # XXX: by default eveything is automatic
 # TODO: use these params to force use of specific proxy/port/IP
@@ -132,14 +135,15 @@
 #     </params>
 #     """
 
-(STATE_INITIAL,
-STATE_AUTH,
-STATE_REQUEST,
-STATE_READY,
-STATE_AUTH_USERPASS,
-STATE_CLIENT_INITIAL,
-STATE_CLIENT_AUTH,
-STATE_CLIENT_REQUEST,
+(
+    STATE_INITIAL,
+    STATE_AUTH,
+    STATE_REQUEST,
+    STATE_READY,
+    STATE_AUTH_USERPASS,
+    STATE_CLIENT_INITIAL,
+    STATE_CLIENT_AUTH,
+    STATE_CLIENT_REQUEST,
 ) = xrange(8)
 
 SOCKS5_VER = 0x05
@@ -167,12 +171,21 @@
 REPLY_ADDR_NOT_SUPPORTED = 0x08
 
 
-ProxyInfos = namedtuple("ProxyInfos", ['host', 'jid', 'port'])
+ProxyInfos = namedtuple("ProxyInfos", ["host", "jid", "port"])
 
 
 class Candidate(object):
-
-    def __init__(self, host, port, type_, priority, jid_, id_=None, priority_local=False, factory=None):
+    def __init__(
+        self,
+        host,
+        port,
+        type_,
+        priority,
+        jid_,
+        id_=None,
+        priority_local=False,
+        factory=None,
+    ):
         """
         @param host(unicode): host IP or domain
         @param port(int): port
@@ -184,8 +197,7 @@
             else priority is used as global one (and local priority is set to 0)
         """
         assert isinstance(jid_, jid.JID)
-        self.host, self.port, self.type, self.jid = (
-            host, int(port), type_, jid_)
+        self.host, self.port, self.type, self.jid = (host, int(port), type_, jid_)
         self.id = id_ if id_ is not None else unicode(uuid.uuid4())
         if priority_local:
             self._local_priority = int(priority)
@@ -204,7 +216,7 @@
         try:
             self.factory.discard()
         except AttributeError:
-            pass # no discard for Socks5ServerFactory
+            pass  # no discard for Socks5ServerFactory
 
     @property
     def local_priority(self):
@@ -218,22 +230,25 @@
         # similar to __unicode__ but we don't show jid and we encode id
         return "Candidate ({0.priority}): host={0.host} port={0.port} type={0.type}{id}".format(
             self,
-            id=u" id={}".format(self.id if self.id is not None else u'').encode('utf-8', 'ignore'),
-            )
+            id=u" id={}".format(self.id if self.id is not None else u"").encode(
+                "utf-8", "ignore"
+            ),
+        )
 
     def __unicode__(self):
         return u"Candidate ({0.priority}): host={0.host} port={0.port} jid={0.jid} type={0.type}{id}".format(
-            self,
-            id=u" id={}".format(self.id if self.id is not None else u''),
-            )
+            self, id=u" id={}".format(self.id if self.id is not None else u"")
+        )
 
     def __eq__(self, other):
         # self.id is is not used in __eq__ as the same candidate can have
         # different ids if proposed by initiator or responder
         try:
-            return (self.host == other.host and
-                    self.port == other.port and
-                    self.jid == other.jid)
+            return (
+                self.host == other.host
+                and self.port == other.port
+                and self.jid == other.jid
+            )
         except (AttributeError, TypeError):
             return False
 
@@ -256,7 +271,7 @@
             multiplier = 10
         else:
             raise exceptions.InternalError(u"Unknown {} type !".format(self.type))
-        return 2**16 * multiplier + self._local_priority
+        return 2 ** 16 * multiplier + self._local_priority
 
     def activate(self, sid, peer_jid, client):
         """Activate the proxy candidate
@@ -269,15 +284,15 @@
         """
         assert self.type == XEP_0065.TYPE_PROXY
         iq_elt = client.IQ()
-        iq_elt['to'] = self.jid.full()
-        query_elt = iq_elt.addElement((NS_BS, 'query'))
-        query_elt['sid'] = sid
-        query_elt.addElement('activate', content=peer_jid.full())
+        iq_elt["to"] = self.jid.full()
+        query_elt = iq_elt.addElement((NS_BS, "query"))
+        query_elt["sid"] = sid
+        query_elt.addElement("activate", content=peer_jid.full())
         return iq_elt.send()
 
     def startTransfer(self, session_hash=None):
         if self.type == XEP_0065.TYPE_PROXY:
-            chunk_size = 4096 # Prosody's proxy reject bigger chunks by default
+            chunk_size = 4096  # Prosody's proxy reject bigger chunks by default
         else:
             chunk_size = None
         self.factory.startTransfer(session_hash, chunk_size=chunk_size)
@@ -291,18 +306,20 @@
     @param sid(unicode): session id
     @return (str): hash
     """
-    return hashlib.sha1((sid + requester_jid.full() + target_jid.full()).encode('utf-8')).hexdigest()
+    return hashlib.sha1(
+        (sid + requester_jid.full() + target_jid.full()).encode("utf-8")
+    ).hexdigest()
 
 
 class SOCKSv5(protocol.Protocol):
-    CHUNK_SIZE = 2**16
+    CHUNK_SIZE = 2 ** 16
 
     def __init__(self, session_hash=None):
         """
         @param session_hash(str): hash of the session
             must only be used in client mode
         """
-        self.connection = defer.Deferred() # called when connection/auth is done
+        self.connection = defer.Deferred()  # called when connection/auth is done
         if session_hash is not None:
             self.server_mode = False
             self._session_hash = session_hash
@@ -318,13 +335,13 @@
         self.addressType = 0
         self.requestType = 0
         self._stream_object = None
-        self.active = False # set to True when protocol is actually used for transfer
-                            # used by factories to know when the finished Deferred can be triggered
+        self.active = False  # set to True when protocol is actually used for transfer
+        # used by factories to know when the finished Deferred can be triggered
 
     @property
     def stream_object(self):
         if self._stream_object is None:
-            self._stream_object = self.getSession()['stream_object']
+            self._stream_object = self.getSession()["stream_object"]
             if self.server_mode:
                 self._stream_object.registerProducer(self.transport, True)
         return self._stream_object
@@ -342,22 +359,22 @@
     def _startNegotiation(self):
         log.debug("starting negotiation (client mode)")
         self.state = STATE_CLIENT_AUTH
-        self.transport.write(struct.pack('!3B', SOCKS5_VER, 1, AUTHMECH_ANON))
+        self.transport.write(struct.pack("!3B", SOCKS5_VER, 1, AUTHMECH_ANON))
 
     def _parseNegotiation(self):
         try:
             # Parse out data
-            ver, nmethod = struct.unpack('!BB', self.buf[:2])
-            methods = struct.unpack('%dB' % nmethod, self.buf[2:nmethod + 2])
+            ver, nmethod = struct.unpack("!BB", self.buf[:2])
+            methods = struct.unpack("%dB" % nmethod, self.buf[2 : nmethod + 2])
 
             # Ensure version is correct
             if ver != 5:
-                self.transport.write(struct.pack('!BB', SOCKS5_VER, AUTHMECH_INVALID))
+                self.transport.write(struct.pack("!BB", SOCKS5_VER, AUTHMECH_INVALID))
                 self.transport.loseConnection()
                 return
 
             # Trim off front of the buffer
-            self.buf = self.buf[nmethod + 2:]
+            self.buf = self.buf[nmethod + 2 :]
 
             # Check for supported auth mechs
             for m in self.supportedAuthMechs:
@@ -368,12 +385,12 @@
                     elif m == AUTHMECH_USERPASS:
                         self.state = STATE_AUTH_USERPASS
                     # Complete negotiation w/ this method
-                    self.transport.write(struct.pack('!BB', SOCKS5_VER, m))
+                    self.transport.write(struct.pack("!BB", SOCKS5_VER, m))
                     return
 
             # No supported mechs found, notify client and close the connection
             log.warning(u"Unsupported authentication mechanism")
-            self.transport.write(struct.pack('!BB', SOCKS5_VER, AUTHMECH_INVALID))
+            self.transport.write(struct.pack("!BB", SOCKS5_VER, AUTHMECH_INVALID))
             self.transport.loseConnection()
         except struct.error:
             pass
@@ -381,34 +398,34 @@
     def _parseUserPass(self):
         try:
             # Parse out data
-            ver, ulen = struct.unpack('BB', self.buf[:2])
-            uname, = struct.unpack('%ds' % ulen, self.buf[2:ulen + 2])
-            plen, = struct.unpack('B', self.buf[ulen + 2])
-            password, = struct.unpack('%ds' % plen, self.buf[ulen + 3:ulen + 3 + plen])
+            ver, ulen = struct.unpack("BB", self.buf[:2])
+            uname, = struct.unpack("%ds" % ulen, self.buf[2 : ulen + 2])
+            plen, = struct.unpack("B", self.buf[ulen + 2])
+            password, = struct.unpack("%ds" % plen, self.buf[ulen + 3 : ulen + 3 + plen])
             # Trim off fron of the buffer
-            self.buf = self.buf[3 + ulen + plen:]
+            self.buf = self.buf[3 + ulen + plen :]
             # Fire event to authenticate user
             if self.authenticateUserPass(uname, password):
                 # Signal success
                 self.state = STATE_REQUEST
-                self.transport.write(struct.pack('!BB', SOCKS5_VER, 0x00))
+                self.transport.write(struct.pack("!BB", SOCKS5_VER, 0x00))
             else:
                 # Signal failure
-                self.transport.write(struct.pack('!BB', SOCKS5_VER, 0x01))
+                self.transport.write(struct.pack("!BB", SOCKS5_VER, 0x01))
                 self.transport.loseConnection()
         except struct.error:
             pass
 
     def sendErrorReply(self, errorcode):
         # Any other address types are not supported
-        result = struct.pack('!BBBBIH', SOCKS5_VER, errorcode, 0, 1, 0, 0)
+        result = struct.pack("!BBBBIH", SOCKS5_VER, errorcode, 0, 1, 0, 0)
         self.transport.write(result)
         self.transport.loseConnection()
 
     def _parseRequest(self):
         try:
             # Parse out data and trim buffer accordingly
-            ver, cmd, rsvd, self.addressType = struct.unpack('!BBBB', self.buf[:4])
+            ver, cmd, rsvd, self.addressType = struct.unpack("!BBBB", self.buf[:4])
 
             # Ensure we actually support the requested address type
             if self.addressType not in self.supportedAddrs:
@@ -417,12 +434,12 @@
 
             # Deal with addresses
             if self.addressType == ADDR_IPV4:
-                addr, port = struct.unpack('!IH', self.buf[4:10])
+                addr, port = struct.unpack("!IH", self.buf[4:10])
                 self.buf = self.buf[10:]
             elif self.addressType == ADDR_DOMAINNAME:
                 nlen = ord(self.buf[4])
-                addr, port = struct.unpack('!%dsH' % nlen, self.buf[5:])
-                self.buf = self.buf[7 + len(addr):]
+                addr, port = struct.unpack("!%dsH" % nlen, self.buf[5:])
+                self.buf = self.buf[7 + len(addr) :]
             else:
                 # Any other address types are not supported
                 self.sendErrorReply(REPLY_ADDR_NOT_SUPPORTED)
@@ -449,13 +466,22 @@
 
     def _makeRequest(self):
         hash_ = self._session_hash
-        request = struct.pack('!5B%dsH' % len(hash_), SOCKS5_VER, CMD_CONNECT, 0, ADDR_DOMAINNAME, len(hash_), hash_, 0)
+        request = struct.pack(
+            "!5B%dsH" % len(hash_),
+            SOCKS5_VER,
+            CMD_CONNECT,
+            0,
+            ADDR_DOMAINNAME,
+            len(hash_),
+            hash_,
+            0,
+        )
         self.transport.write(request)
         self.state = STATE_CLIENT_REQUEST
 
     def _parseRequestReply(self):
         try:
-            ver, rep, rsvd, self.addressType = struct.unpack('!BBBB', self.buf[:4])
+            ver, rep, rsvd, self.addressType = struct.unpack("!BBBB", self.buf[:4])
             # Ensure we actually support the requested address type
             if self.addressType not in self.supportedAddrs:
                 self.sendErrorReply(REPLY_ADDR_NOT_SUPPORTED)
@@ -463,12 +489,12 @@
 
             # Deal with addresses
             if self.addressType == ADDR_IPV4:
-                addr, port = struct.unpack('!IH', self.buf[4:10])
+                addr, port = struct.unpack("!IH", self.buf[4:10])
                 self.buf = self.buf[10:]
             elif self.addressType == ADDR_DOMAINNAME:
                 nlen = ord(self.buf[4])
-                addr, port = struct.unpack('!%dsH' % nlen, self.buf[5:])
-                self.buf = self.buf[7 + len(addr):]
+                addr, port = struct.unpack("!%dsH" % nlen, self.buf[5:])
+                self.buf = self.buf[7 + len(addr) :]
             else:
                 # Any other address types are not supported
                 self.sendErrorReply(REPLY_ADDR_NOT_SUPPORTED)
@@ -487,7 +513,11 @@
             return None
 
     def connectionMade(self):
-        log.debug(u"Socks5 connectionMade (mode = {})".format("server" if self.state == STATE_INITIAL else "client"))
+        log.debug(
+            u"Socks5 connectionMade (mode = {})".format(
+                "server" if self.state == STATE_INITIAL else "client"
+            )
+        )
         if self.state == STATE_CLIENT_INITIAL:
             self._startNegotiation()
 
@@ -495,8 +525,11 @@
         # Check that this session is expected
         if not self.factory.addToSession(addr, self):
             self.sendErrorReply(REPLY_CONN_REFUSED)
-            log.warning(u"Unexpected connection request received from {host}"
-                .format(host=self.transport.getPeer().host))
+            log.warning(
+                u"Unexpected connection request received from {host}".format(
+                    host=self.transport.getPeer().host
+                )
+            )
             return
         self._session_hash = addr
         self.connectCompleted(addr, 0)
@@ -519,10 +552,20 @@
 
     def connectCompleted(self, remotehost, remoteport):
         if self.addressType == ADDR_IPV4:
-            result = struct.pack('!BBBBIH', SOCKS5_VER, REPLY_SUCCESS, 0, 1, remotehost, remoteport)
+            result = struct.pack(
+                "!BBBBIH", SOCKS5_VER, REPLY_SUCCESS, 0, 1, remotehost, remoteport
+            )
         elif self.addressType == ADDR_DOMAINNAME:
-            result = struct.pack('!BBBBB%dsH' % len(remotehost), SOCKS5_VER, REPLY_SUCCESS, 0,
-                                 ADDR_DOMAINNAME, len(remotehost), remotehost, remoteport)
+            result = struct.pack(
+                "!BBBBB%dsH" % len(remotehost),
+                SOCKS5_VER,
+                REPLY_SUCCESS,
+                0,
+                ADDR_DOMAINNAME,
+                len(remotehost),
+                remotehost,
+                remoteport,
+            )
         self.transport.write(result)
         self.state = STATE_READY
 
@@ -553,7 +596,7 @@
         if self.state == STATE_CLIENT_REQUEST:
             self._parseRequestReply()
         if self.state == STATE_CLIENT_AUTH:
-            ver, method = struct.unpack('!BB', buf)
+            ver, method = struct.unpack("!BB", buf)
             self.buf = self.buf[2:]
             if ver != SOCKS5_VER or method != AUTHMECH_ANON:
                 self.transport.loseConnection()
@@ -564,7 +607,7 @@
         log.debug(u"Socks5 connection lost: {}".format(reason.value))
         if self.state != STATE_READY:
             self.connection.errback(reason)
-        if self.server_mode :
+        if self.server_mode:
             self.factory.removeFromSession(self._session_hash, self, reason)
 
 
@@ -583,7 +626,7 @@
     def startTransfer(self, session_hash, chunk_size=None):
         session = self.getSession(session_hash)
         try:
-            protocol = session['protocols'][0]
+            protocol = session["protocols"][0]
         except (KeyError, IndexError):
             log.error(u"Can't start file transfer, can't find protocol")
         else:
@@ -603,7 +646,7 @@
         except KeyError:
             return False
         else:
-            session_data.setdefault('protocols', []).append(protocol)
+            session_data.setdefault("protocols", []).append(protocol)
             return True
 
     def removeFromSession(self, session_hash, protocol, reason):
@@ -616,7 +659,7 @@
         @param reason(failure.Failure): reason of the removal
         """
         try:
-            protocols = self.getSession(session_hash)['protocols']
+            protocols = self.getSession(session_hash)["protocols"]
             protocols.remove(protocol)
         except (KeyError, ValueError):
             log.error(u"Protocol not found in session while it should be there")
@@ -686,10 +729,10 @@
 
 class XEP_0065(object):
     NAMESPACE = NS_BS
-    TYPE_DIRECT = 'direct'
-    TYPE_ASSISTED = 'assisted'
-    TYPE_TUNEL = 'tunel'
-    TYPE_PROXY = 'proxy'
+    TYPE_DIRECT = "direct"
+    TYPE_ASSISTED = "assisted"
+    TYPE_TUNEL = "tunel"
+    TYPE_PROXY = "proxy"
     Candidate = Candidate
 
     def __init__(self, host):
@@ -698,16 +741,16 @@
 
         # session data
         self.hash_clients_map = {}  # key: hash of the transfer session, value: session data
-        self._cache_proxies = {} # key: server jid, value: proxy data
+        self._cache_proxies = {}  # key: server jid, value: proxy data
 
         # misc data
         self._server_factory = None
         self._external_port = None
 
         # plugins shortcuts
-        self._ip = self.host.plugins['IP']
+        self._ip = self.host.plugins["IP"]
         try:
-            self._np = self.host.plugins['NAT-PORT']
+            self._np = self.host.plugins["NAT-PORT"]
         except KeyError:
             log.debug(u"NAT Port plugin not available")
             self._np = None
@@ -739,16 +782,22 @@
                 try:
                     listening_port = reactor.listenTCP(port, self._server_factory)
                 except internet_error.CannotListenError as e:
-                    log.debug(u"Cannot listen on port {port}: {err_msg}{err_num}".format(
-                        port=port,
-                        err_msg=e.socketError.strerror,
-                        err_num=u' (error code: {})'.format(e.socketError.errno),
-                        ))
+                    log.debug(
+                        u"Cannot listen on port {port}: {err_msg}{err_num}".format(
+                            port=port,
+                            err_msg=e.socketError.strerror,
+                            err_num=u" (error code: {})".format(e.socketError.errno),
+                        )
+                    )
                 else:
                     self._server_factory_port = listening_port.getHost().port
                     break
 
-            log.info(_("Socks5 Stream server launched on port {}").format(self._server_factory_port))
+            log.info(
+                _("Socks5 Stream server launched on port {}").format(
+                    self._server_factory_port
+                )
+            )
         return self._server_factory
 
     @defer.inlineCallbacks
@@ -759,36 +808,43 @@
         @return ((D)(ProxyInfos, None)): Found proxy infos,
             or None if not acceptable proxy is found
         """
+
         def notFound(server):
             log.info(u"No proxy found on this server")
             self._cache_proxies[server] = None
             defer.returnValue(None)
+
         server = client.jid.host
         try:
             defer.returnValue(self._cache_proxies[server])
         except KeyError:
             pass
         try:
-            proxy = (yield self.host.findServiceEntities(client, 'proxy', 'bytestreams')).pop()
+            proxy = (
+                yield self.host.findServiceEntities(client, "proxy", "bytestreams")
+            ).pop()
         except (defer.CancelledError, StopIteration, KeyError):
             notFound(server)
-        iq_elt = client.IQ('get')
-        iq_elt['to'] = proxy.full()
-        iq_elt.addElement((NS_BS, 'query'))
+        iq_elt = client.IQ("get")
+        iq_elt["to"] = proxy.full()
+        iq_elt.addElement((NS_BS, "query"))
 
         try:
             result_elt = yield iq_elt.send()
         except jabber_error.StanzaError as failure:
-            log.warning(u"Error while requesting proxy info on {jid}: {error}"
-                .format(proxy.full(), failure))
+            log.warning(
+                u"Error while requesting proxy info on {jid}: {error}".format(
+                    proxy.full(), failure
+                )
+            )
             notFound(server)
 
         try:
-            query_elt = result_elt.elements(NS_BS, 'query').next()
-            streamhost_elt = query_elt.elements(NS_BS, 'streamhost').next()
-            host = streamhost_elt['host']
-            jid_ = streamhost_elt['jid']
-            port = streamhost_elt['port']
+            query_elt = result_elt.elements(NS_BS, "query").next()
+            streamhost_elt = query_elt.elements(NS_BS, "streamhost").next()
+            host = streamhost_elt["host"]
+            jid_ = streamhost_elt["jid"]
+            port = streamhost_elt["port"]
             if not all((host, jid, port)):
                 raise KeyError
             jid_ = jid.JID(jid_)
@@ -818,7 +874,9 @@
                 if self._np is None:
                     log.warning(u"NAT port plugin not available, we can't map port")
                 else:
-                    ext_port = yield self._np.mapPort(local_port, desc=u"SaT socks5 stream")
+                    ext_port = yield self._np.mapPort(
+                        local_port, desc=u"SaT socks5 stream"
+                    )
                     if ext_port is None:
                         log.warning(u"Can't map NAT port")
                     else:
@@ -843,17 +901,56 @@
 
         # the preferred direct connection
         ip = local_ips.pop(0)
-        candidates.append(Candidate(ip, local_port, XEP_0065.TYPE_DIRECT, PRIORITY_BEST_DIRECT, client.jid, priority_local=True, factory=server_factory))
+        candidates.append(
+            Candidate(
+                ip,
+                local_port,
+                XEP_0065.TYPE_DIRECT,
+                PRIORITY_BEST_DIRECT,
+                client.jid,
+                priority_local=True,
+                factory=server_factory,
+            )
+        )
         for ip in local_ips:
-            candidates.append(Candidate(ip, local_port, XEP_0065.TYPE_DIRECT, PRIORITY_DIRECT, client.jid, priority_local=True, factory=server_factory))
+            candidates.append(
+                Candidate(
+                    ip,
+                    local_port,
+                    XEP_0065.TYPE_DIRECT,
+                    PRIORITY_DIRECT,
+                    client.jid,
+                    priority_local=True,
+                    factory=server_factory,
+                )
+            )
 
         # then the assisted one
         if ext_port is not None:
-            candidates.append(Candidate(external_ip, ext_port, XEP_0065.TYPE_ASSISTED, PRIORITY_ASSISTED, client.jid, priority_local=True, factory=server_factory))
+            candidates.append(
+                Candidate(
+                    external_ip,
+                    ext_port,
+                    XEP_0065.TYPE_ASSISTED,
+                    PRIORITY_ASSISTED,
+                    client.jid,
+                    priority_local=True,
+                    factory=server_factory,
+                )
+            )
 
         # finally the proxy
         if proxy:
-            candidates.append(Candidate(proxy.host, proxy.port, XEP_0065.TYPE_PROXY, PRIORITY_PROXY, proxy.jid, priority_local=True))
+            candidates.append(
+                Candidate(
+                    proxy.host,
+                    proxy.port,
+                    XEP_0065.TYPE_PROXY,
+                    PRIORITY_PROXY,
+                    proxy.jid,
+                    priority_local=True,
+                )
+            )
 
         # should be already sorted, but just in case the priorities get weird
         candidates.sort(key=lambda c: c.priority, reverse=True)
@@ -870,7 +967,9 @@
         candidate.factory.connector = connector
         return candidate.factory.connection
 
-    def connectCandidate(self, client, candidate, session_hash, peer_session_hash=None, delay=None):
+    def connectCandidate(
+        self, client, candidate, session_hash, peer_session_hash=None, delay=None
+    ):
         """Connect to a candidate
 
         Connection will be done with a Socks5ClientFactory
@@ -900,16 +999,30 @@
         d.addCallback(self._addConnector, candidate)
         return d
 
-    def tryCandidates(self, client, candidates, session_hash, peer_session_hash, connection_cb=None, connection_eb=None):
+    def tryCandidates(
+        self,
+        client,
+        candidates,
+        session_hash,
+        peer_session_hash,
+        connection_cb=None,
+        connection_eb=None,
+    ):
         defers_list = []
 
         for candidate in candidates:
             delay = CANDIDATE_DELAY * len(defers_list)
             if candidate.type == XEP_0065.TYPE_PROXY:
                 delay += CANDIDATE_DELAY_PROXY
-            d = self.connectCandidate(client, candidate, session_hash, peer_session_hash, delay)
+            d = self.connectCandidate(
+                client, candidate, session_hash, peer_session_hash, delay
+            )
             if connection_cb is not None:
-                d.addCallback(lambda dummy, candidate=candidate, client=client: connection_cb(client, candidate))
+                d.addCallback(
+                    lambda dummy, candidate=candidate, client=client: connection_cb(
+                        client, candidate
+                    )
+                )
             if connection_eb is not None:
                 d.addErrback(connection_eb, client, candidate)
             defers_list.append(d)
@@ -942,9 +1055,11 @@
             if failure.check(defer.CancelledError):
                 log.debug(u"Connection of {} has been cancelled".format(candidate))
             else:
-                log.info(u"Connection of {candidate} Failed: {error}".format(
-                    candidate = candidate,
-                    error = failure.value))
+                log.info(
+                    u"Connection of {candidate} Failed: {error}".format(
+                        candidate=candidate, error=failure.value
+                    )
+                )
             candidates[candidates.index(candidate)] = None
 
         def allTested(self):
@@ -952,7 +1067,14 @@
             good_candidates = [c for c in candidates if c]
             return good_candidates[0] if good_candidates else None
 
-        defer_candidates = self.tryCandidates(client, candidates, session_hash, peer_session_hash, connectionCb, connectionEb)
+        defer_candidates = self.tryCandidates(
+            client,
+            candidates,
+            session_hash,
+            peer_session_hash,
+            connectionCb,
+            connectionEb,
+        )
         d_list = defer.DeferredList(defer_candidates)
         d_list.addCallback(allTested)
         return d_list
@@ -977,11 +1099,13 @@
         @param failure_(None, failure.Failure): None if eveything was fine, a failure else
         @return (None, failure.Failure): failure_ is returned
         """
-        log.debug(u'Cleaning session with hash {hash}{id}: {reason}'.format(
-            hash=session_hash,
-            reason='' if failure_ is None else failure_.value,
-            id='' if sid is None else u' (id: {})'.format(sid),
-            ))
+        log.debug(
+            u"Cleaning session with hash {hash}{id}: {reason}".format(
+                hash=session_hash,
+                reason="" if failure_ is None else failure_.value,
+                id="" if sid is None else u" (id: {})".format(sid),
+            )
+        )
 
         try:
             assert self.hash_clients_map[session_hash] == client
@@ -1004,7 +1128,7 @@
             del client._s5b_sessions[session_hash]
 
         try:
-            session_data['timer'].cancel()
+            session_data["timer"].cancel()
         except (internet_error.AlreadyCalled, internet_error.AlreadyCancelled):
             pass
 
@@ -1025,19 +1149,19 @@
         session_data[client] = client
 
         def gotCandidates(candidates):
-            session_data['candidates'] = candidates
+            session_data["candidates"] = candidates
             iq_elt = client.IQ()
             iq_elt["from"] = client.jid.full()
             iq_elt["to"] = to_jid.full()
-            query_elt = iq_elt.addElement((NS_BS, 'query'))
-            query_elt['mode'] = 'tcp'
-            query_elt['sid'] = sid
+            query_elt = iq_elt.addElement((NS_BS, "query"))
+            query_elt["mode"] = "tcp"
+            query_elt["sid"] = sid
 
             for candidate in candidates:
-                streamhost = query_elt.addElement('streamhost')
-                streamhost['host'] = candidate.host
-                streamhost['port'] = str(candidate.port)
-                streamhost['jid'] = candidate.jid.full()
+                streamhost = query_elt.addElement("streamhost")
+                streamhost["host"] = candidate.host
+                streamhost["port"] = str(candidate.port)
+                streamhost["jid"] = candidate.jid.full()
                 log.debug(u"Candidate proposed: {}".format(candidate))
 
             d = iq_elt.send()
@@ -1055,31 +1179,39 @@
         @param iq_elt(domish.Element): <iq> result
         """
         try:
-            query_elt = iq_elt.elements(NS_BS, 'query').next()
-            streamhost_used_elt = query_elt.elements(NS_BS, 'streamhost-used').next()
+            query_elt = iq_elt.elements(NS_BS, "query").next()
+            streamhost_used_elt = query_elt.elements(NS_BS, "streamhost-used").next()
         except StopIteration:
             log.warning(u"No streamhost found in stream query")
             # FIXME: must clean session
             return
 
-        streamhost_jid = jid.JID(streamhost_used_elt['jid'])
+        streamhost_jid = jid.JID(streamhost_used_elt["jid"])
         try:
-            candidate = (c for c in session_data['candidates'] if c.jid == streamhost_jid).next()
+            candidate = (
+                c for c in session_data["candidates"] if c.jid == streamhost_jid
+            ).next()
         except StopIteration:
-            log.warning(u"Candidate [{jid}] is unknown !".format(jid=streamhost_jid.full()))
+            log.warning(
+                u"Candidate [{jid}] is unknown !".format(jid=streamhost_jid.full())
+            )
             return
         else:
             log.info(u"Candidate choosed by target: {}".format(candidate))
 
         if candidate.type == XEP_0065.TYPE_PROXY:
             log.info(u"A Socks5 proxy is used")
-            d = self.connectCandidate(client, candidate, session_data['hash'])
-            d.addCallback(lambda dummy: candidate.activate(session_data['id'], session_data['peer_jid'], client))
+            d = self.connectCandidate(client, candidate, session_data["hash"])
+            d.addCallback(
+                lambda dummy: candidate.activate(
+                    session_data["id"], session_data["peer_jid"], client
+                )
+            )
             d.addErrback(self._activationEb)
         else:
             d = defer.succeed(None)
 
-        d.addCallback(lambda dummy: candidate.startTransfer(session_data['hash']))
+        d.addCallback(lambda dummy: candidate.startTransfer(session_data["hash"]))
 
     def _activationEb(self, failure):
         log.warning(u"Proxy activation error: {}".format(failure.value))
@@ -1105,7 +1237,7 @@
         @return (dict): session data
         """
         if sid in client.xep_0065_sid_session:
-            raise exceptions.ConflictError(u'A session with this id already exists !')
+            raise exceptions.ConflictError(u"A session with this id already exists !")
         if requester:
             session_hash = getSessionHash(client.jid, to_jid, sid)
             session_data = self._registerHash(client, session_hash, stream_object)
@@ -1115,15 +1247,19 @@
             session_d.addBoth(self.killSession, session_hash, sid, client)
             session_data = client._s5b_sessions[session_hash] = {
                 DEFER_KEY: session_d,
-                TIMER_KEY: reactor.callLater(TIMEOUT, self._timeOut, session_hash, client),
-                }
+                TIMER_KEY: reactor.callLater(
+                    TIMEOUT, self._timeOut, session_hash, client
+                ),
+            }
         client.xep_0065_sid_session[sid] = session_data
         session_data.update(
-            {'id': sid,
-             'peer_jid': to_jid,
-             'stream_object': stream_object,
-             'hash': session_hash,
-            })
+            {
+                "id": sid,
+                "peer_jid": to_jid,
+                "stream_object": stream_object,
+                "hash": session_hash,
+            }
+        )
 
         return session_data
 
@@ -1141,7 +1277,7 @@
         """
         if client is None:
             try:
-                client =  self.hash_clients_map[session_hash]
+                client = self.hash_clients_map[session_hash]
             except KeyError as e:
                 log.warning(u"The requested session doesn't exists !")
                 raise e
@@ -1167,10 +1303,10 @@
         session_data = client._s5b_sessions[session_hash] = {
             DEFER_KEY: session_d,
             TIMER_KEY: reactor.callLater(TIMEOUT, self._timeOut, session_hash, client),
-            }
+        }
 
         if stream_object is not None:
-            session_data['stream_object'] = stream_object
+            session_data["stream_object"] = stream_object
 
         assert session_hash not in self.hash_clients_map
         self.hash_clients_map[session_hash] = client
@@ -1180,22 +1316,22 @@
     def associateStreamObject(self, client, session_hash, stream_object):
         """Associate a stream object with  a session"""
         session_data = self.getSession(client, session_hash)
-        assert 'stream_object' not in session_data
-        session_data['stream_object'] = stream_object
+        assert "stream_object" not in session_data
+        session_data["stream_object"] = stream_object
 
     def streamQuery(self, iq_elt, client):
         log.debug(u"BS stream query")
 
         iq_elt.handled = True
 
-        query_elt = iq_elt.elements(NS_BS, 'query').next()
+        query_elt = iq_elt.elements(NS_BS, "query").next()
         try:
-            sid = query_elt['sid']
+            sid = query_elt["sid"]
         except KeyError:
             log.warning(u"Invalid bystreams request received")
             return client.sendError(iq_elt, "bad-request")
 
-        streamhost_elts = list(query_elt.elements(NS_BS, 'streamhost'))
+        streamhost_elts = list(query_elt.elements(NS_BS, "streamhost"))
         if not streamhost_elts:
             return client.sendError(iq_elt, "bad-request")
 
@@ -1203,7 +1339,7 @@
             session_data = client.xep_0065_sid_session[sid]
         except KeyError:
             log.warning(u"Ignoring unexpected BS transfer: {}".format(sid))
-            return client.sendError(iq_elt, 'not-acceptable')
+            return client.sendError(iq_elt, "not-acceptable")
 
         peer_jid = session_data["peer_jid"] = jid.JID(iq_elt["from"])
 
@@ -1211,7 +1347,7 @@
         nb_sh = len(streamhost_elts)
         for idx, sh_elt in enumerate(streamhost_elts):
             try:
-                host, port, jid_ = sh_elt['host'], sh_elt['port'], jid.JID(sh_elt['jid'])
+                host, port, jid_ = sh_elt["host"], sh_elt["port"], jid.JID(sh_elt["jid"])
             except KeyError:
                 log.warning(u"malformed streamhost element")
                 return client.sendError(iq_elt, "bad-request")
@@ -1225,19 +1361,19 @@
         for candidate in candidates:
             log.info(u"Candidate proposed: {}".format(candidate))
 
-        d = self.getBestCandidate(client, candidates, session_data['hash'])
+        d = self.getBestCandidate(client, candidates, session_data["hash"])
         d.addCallback(self._ackStream, iq_elt, session_data, client)
 
     def _ackStream(self, candidate, iq_elt, session_data, client):
         if candidate is None:
             log.info("No streamhost candidate worked, we have to end negotiation")
-            return client.sendError(iq_elt, 'item-not-found')
+            return client.sendError(iq_elt, "item-not-found")
         log.info(u"We choose: {}".format(candidate))
-        result_elt = xmlstream.toResponse(iq_elt, 'result')
-        query_elt = result_elt.addElement((NS_BS, 'query'))
-        query_elt['sid'] = session_data['id']
-        streamhost_used_elt = query_elt.addElement('streamhost-used')
-        streamhost_used_elt['jid'] = candidate.jid.full()
+        result_elt = xmlstream.toResponse(iq_elt, "result")
+        query_elt = result_elt.addElement((NS_BS, "query"))
+        query_elt["sid"] = session_data["id"]
+        streamhost_used_elt = query_elt.addElement("streamhost-used")
+        streamhost_used_elt["jid"] = candidate.jid.full()
         client.send(result_elt)
 
 
@@ -1249,10 +1385,12 @@
         self.host = plugin_parent.host
 
     def connectionInitialized(self):
-        self.xmlstream.addObserver(BS_REQUEST, self.plugin_parent.streamQuery, client=self.parent)
+        self.xmlstream.addObserver(
+            BS_REQUEST, self.plugin_parent.streamQuery, client=self.parent
+        )
 
-    def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
+    def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
         return [disco.DiscoFeature(NS_BS)]
 
-    def getDiscoItems(self, requestor, target, nodeIdentifier=''):
+    def getDiscoItems(self, requestor, target, nodeIdentifier=""):
         return []
--- a/sat/plugins/plugin_xep_0070.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_xep_0070.py	Wed Jun 27 20:14:46 2018 +0200
@@ -21,25 +21,27 @@
 from sat.core.log import getLogger
 from twisted.words.protocols.jabber import xmlstream
 from twisted.words.protocols import jabber
+
 log = getLogger(__name__)
 from sat.tools import xml_tools
 
 from wokkel import disco, iwokkel
 from zope.interface import implements
+
 try:
     from twisted.words.protocols.xmlstream import XMPPHandler
 except ImportError:
     from wokkel.subprotocols import XMPPHandler
 
 
-NS_HTTP_AUTH = 'http://jabber.org/protocol/http-auth'
+NS_HTTP_AUTH = "http://jabber.org/protocol/http-auth"
 
-IQ = 'iq'
-IQ_GET = '/'+IQ+'[@type="get"]'
+IQ = "iq"
+IQ_GET = "/" + IQ + '[@type="get"]'
 IQ_HTTP_AUTH_REQUEST = IQ_GET + '/confirm[@xmlns="' + NS_HTTP_AUTH + '"]'
 
-MSG = 'message'
-MSG_GET = '/'+MSG+'[@type="normal"]'
+MSG = "message"
+MSG_GET = "/" + MSG + '[@type="normal"]'
 MSG_HTTP_AUTH_REQUEST = MSG_GET + '/confirm[@xmlns="' + NS_HTTP_AUTH + '"]'
 
 
@@ -51,7 +53,7 @@
     C.PI_DEPENDENCIES: [],
     C.PI_MAIN: "XEP_0070",
     C.PI_HANDLER: "yes",
-    C.PI_DESCRIPTION: _("""Implementation of HTTP Requests via XMPP""")
+    C.PI_DESCRIPTION: _("""Implementation of HTTP Requests via XMPP"""),
 }
 
 
@@ -88,16 +90,20 @@
 
     def _treatHttpAuthRequest(self, elt, stanzaType, client):
         elt.handled = True
-        auth_elt = elt.elements(NS_HTTP_AUTH, 'confirm').next()
-        auth_id = auth_elt['id']
-        auth_method = auth_elt['method']
-        auth_url = auth_elt['url']
+        auth_elt = elt.elements(NS_HTTP_AUTH, "confirm").next()
+        auth_id = auth_elt["id"]
+        auth_method = auth_elt["method"]
+        auth_url = auth_elt["url"]
         self._dictRequest[client] = (auth_id, auth_method, auth_url, stanzaType, elt)
 
-        confirm_ui = xml_tools.XMLUI("form", title=D_(u"Auth confirmation"), submit_id='')
-        confirm_ui.addText(D_(u"{} needs to validate your identity, do you agree ?".format(auth_url)))
+        confirm_ui = xml_tools.XMLUI("form", title=D_(u"Auth confirmation"), submit_id="")
+        confirm_ui.addText(
+            D_(u"{} needs to validate your identity, do you agree ?".format(auth_url))
+        )
         confirm_ui.addText(D_(u"Validation code : {}".format(auth_id)))
-        confirm_ui.addText(D_(u"Please check that this code is the same as on {}".format(auth_url)))
+        confirm_ui.addText(
+            D_(u"Please check that this code is the same as on {}".format(auth_url))
+        )
         confirm_ui.addText(u"")
         confirm_ui.addText(D_(u"Submit to authorize, cancel otherwise."))
         d = xml_tools.deferredUI(self.host, confirm_ui, chained=False)
@@ -107,7 +113,7 @@
     def _authRequestCallback(self, result, profile):
         client = self.host.getClient(profile)
         try:
-            cancelled = result['cancelled']
+            cancelled = result["cancelled"]
         except KeyError:
             cancelled = False
 
@@ -119,23 +125,25 @@
             authorized = False
         else:
             try:
-                auth_id, auth_method, auth_url, stanzaType, elt = self._dictRequest[client]
+                auth_id, auth_method, auth_url, stanzaType, elt = self._dictRequest[
+                    client
+                ]
                 del self._dictRequest[client]
                 authorized = True
             except KeyError:
                 authorized = False
 
         if authorized:
-            if (stanzaType == IQ):
+            if stanzaType == IQ:
                 # iq
                 log.debug(_(u"XEP-0070 reply iq"))
-                iq_result_elt = xmlstream.toResponse(elt, 'result')
+                iq_result_elt = xmlstream.toResponse(elt, "result")
                 client.send(iq_result_elt)
-            elif (stanzaType == MSG):
+            elif stanzaType == MSG:
                 # message
                 log.debug(_(u"XEP-0070 reply message"))
-                msg_result_elt = xmlstream.toResponse(elt, 'result')
-                msg_result_elt.addChild(elt.elements(NS_HTTP_AUTH, 'confirm').next())
+                msg_result_elt = xmlstream.toResponse(elt, "result")
+                msg_result_elt.addChild(elt.elements(NS_HTTP_AUTH, "confirm").next())
                 client.send(msg_result_elt)
         else:
             log.debug(_(u"XEP-0070 reply error"))
@@ -152,11 +160,19 @@
         self.profile = profile
 
     def connectionInitialized(self):
-        self.xmlstream.addObserver(IQ_HTTP_AUTH_REQUEST, self.plugin_parent.onHttpAuthRequestIQ, client=self.parent)
-        self.xmlstream.addObserver(MSG_HTTP_AUTH_REQUEST, self.plugin_parent.onHttpAuthRequestMsg, client=self.parent)
+        self.xmlstream.addObserver(
+            IQ_HTTP_AUTH_REQUEST,
+            self.plugin_parent.onHttpAuthRequestIQ,
+            client=self.parent,
+        )
+        self.xmlstream.addObserver(
+            MSG_HTTP_AUTH_REQUEST,
+            self.plugin_parent.onHttpAuthRequestMsg,
+            client=self.parent,
+        )
 
-    def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
+    def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
         return [disco.DiscoFeature(NS_HTTP_AUTH)]
 
-    def getDiscoItems(self, requestor, target, nodeIdentifier=''):
+    def getDiscoItems(self, requestor, target, nodeIdentifier=""):
         return []
--- a/sat/plugins/plugin_xep_0071.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_xep_0071.py	Wed Jun 27 20:14:46 2018 +0200
@@ -21,24 +21,28 @@
 from sat.core.constants import Const as C
 from sat.core import exceptions
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from sat.tools.common import data_format
 
 from twisted.internet import defer
 from wokkel import disco, iwokkel
 from zope.interface import implements
+
 # from lxml import etree
 try:
     from lxml import html
 except ImportError:
-    raise exceptions.MissingModule(u"Missing module lxml, please download/install it from http://lxml.de/")
+    raise exceptions.MissingModule(
+        u"Missing module lxml, please download/install it from http://lxml.de/"
+    )
 try:
     from twisted.words.protocols.xmlstream import XMPPHandler
 except ImportError:
     from wokkel.subprotocols import XMPPHandler
 
-NS_XHTML_IM = 'http://jabber.org/protocol/xhtml-im'
-NS_XHTML = 'http://www.w3.org/1999/xhtml'
+NS_XHTML_IM = "http://jabber.org/protocol/xhtml-im"
+NS_XHTML = "http://www.w3.org/1999/xhtml"
 
 PLUGIN_INFO = {
     C.PI_NAME: "XHTML-IM Plugin",
@@ -48,28 +52,39 @@
     C.PI_DEPENDENCIES: ["TEXT-SYNTAXES"],
     C.PI_MAIN: "XEP_0071",
     C.PI_HANDLER: "yes",
-    C.PI_DESCRIPTION: _("""Implementation of XHTML-IM""")
+    C.PI_DESCRIPTION: _("""Implementation of XHTML-IM"""),
 }
 
 allowed = {
-          "a": set(["href", "style", "type"]),
-          "blockquote": set(["style"]),
-          "body": set(["style"]),
-          "br": set([]),
-          "cite": set(["style"]),
-          "em": set([]),
-          "img": set(["alt", "height", "src", "style", "width"]),
-          "li": set(["style"]),
-          "ol": set(["style"]),
-          "p": set(["style"]),
-          "span": set(["style"]),
-          "strong": set([]),
-          "ul": set(["style"]),
-          }
+    "a": set(["href", "style", "type"]),
+    "blockquote": set(["style"]),
+    "body": set(["style"]),
+    "br": set([]),
+    "cite": set(["style"]),
+    "em": set([]),
+    "img": set(["alt", "height", "src", "style", "width"]),
+    "li": set(["style"]),
+    "ol": set(["style"]),
+    "p": set(["style"]),
+    "span": set(["style"]),
+    "strong": set([]),
+    "ul": set(["style"]),
+}
 
-styles_allowed = ["background-color", "color", "font-family", "font-size", "font-style", "font-weight", "margin-left", "margin-right", "text-align", "text-decoration"]
+styles_allowed = [
+    "background-color",
+    "color",
+    "font-family",
+    "font-size",
+    "font-style",
+    "font-weight",
+    "margin-left",
+    "margin-right",
+    "text-align",
+    "text-decoration",
+]
 
-blacklist = ['script'] # tag that we have to kill (we don't keep content)
+blacklist = ["script"]  # tag that we have to kill (we don't keep content)
 
 
 class XEP_0071(object):
@@ -79,7 +94,12 @@
         log.info(_("XHTML-IM plugin initialization"))
         self.host = host
         self._s = self.host.plugins["TEXT-SYNTAXES"]
-        self._s.addSyntax(self.SYNTAX_XHTML_IM, lambda xhtml: xhtml, self.XHTML2XHTML_IM, [self._s.OPT_HIDDEN])
+        self._s.addSyntax(
+            self.SYNTAX_XHTML_IM,
+            lambda xhtml: xhtml,
+            self.XHTML2XHTML_IM,
+            [self._s.OPT_HIDDEN],
+        )
         host.trigger.add("MessageReceived", self.messageReceivedTrigger)
         host.trigger.add("sendMessage", self.sendMessageTrigger)
 
@@ -97,17 +117,23 @@
         # TODO: check if text only body is empty, then try to convert XHTML-IM to pure text and show a warning message
         def converted(xhtml, lang):
             if lang:
-                data['extra']['xhtml_{}'.format(lang)] = xhtml
+                data["extra"]["xhtml_{}".format(lang)] = xhtml
             else:
-                data['extra']['xhtml'] = xhtml
+                data["extra"]["xhtml"] = xhtml
 
         defers = []
         for body_elt in body_elts:
-            lang = body_elt.getAttribute((C.NS_XML, 'lang'), '')
-            treat_d = defer.succeed(None)  # deferred used for treatments
-            if self.host.trigger.point("xhtml_post_treat", client, message_elt, body_elt, lang, treat_d):
+            lang = body_elt.getAttribute((C.NS_XML, "lang"), "")
+            treat_d = defer.succeed(None)  #  deferred used for treatments
+            if self.host.trigger.point(
+                "xhtml_post_treat", client, message_elt, body_elt, lang, treat_d
+            ):
                 continue
-            treat_d.addCallback(lambda dummy: self._s.convert(body_elt.toXml(), self.SYNTAX_XHTML_IM, safe=True))
+            treat_d.addCallback(
+                lambda dummy: self._s.convert(
+                    body_elt.toXml(), self.SYNTAX_XHTML_IM, safe=True
+                )
+            )
             treat_d.addCallback(converted, lang)
             defers.append(treat_d)
 
@@ -116,15 +142,15 @@
         return d_list
 
     def _fill_body_text(self, text, data, lang):
-        data['message'][lang or ''] = text
-        message_elt = data['xml']
+        data["message"][lang or ""] = text
+        message_elt = data["xml"]
         body_elt = message_elt.addElement("body", content=text)
         if lang:
-            body_elt[(C.NS_XML, 'lang')] = lang
+            body_elt[(C.NS_XML, "lang")] = lang
 
     def _check_body_text(self, data, lang, markup, syntax, defers):
         """check if simple text message exists, and fill if needed"""
-        if not (lang or '') in data['message']:
+        if not (lang or "") in data["message"]:
             d = self._s.convert(markup, syntax, self._s.SYNTAX_TEXT)
             d.addCallback(self._fill_body_text, data, lang)
             defers.append(d)
@@ -136,30 +162,30 @@
         """
         # at this point, either ['extra']['rich'] or ['extra']['xhtml'] exists
         # but both can't exist at the same time
-        message_elt = data['xml']
-        html_elt = message_elt.addElement((NS_XHTML_IM, 'html'))
+        message_elt = data["xml"]
+        html_elt = message_elt.addElement((NS_XHTML_IM, "html"))
 
         def syntax_converted(xhtml_im, lang):
-            body_elt = html_elt.addElement((NS_XHTML, 'body'))
+            body_elt = html_elt.addElement((NS_XHTML, "body"))
             if lang:
-                body_elt[(C.NS_XML, 'lang')] = lang
-                data['extra']['xhtml_{}'.format(lang)] = xhtml_im
+                body_elt[(C.NS_XML, "lang")] = lang
+                data["extra"]["xhtml_{}".format(lang)] = xhtml_im
             else:
-                data['extra']['xhtml'] = xhtml_im
+                data["extra"]["xhtml"] = xhtml_im
             body_elt.addRawXml(xhtml_im)
 
         syntax = self._s.getCurrentSyntax(client.profile)
         defers = []
-        if u'xhtml' in data['extra']:
+        if u"xhtml" in data["extra"]:
             # we have directly XHTML
-            for lang, xhtml in data_format.getSubDict('xhtml', data['extra']):
+            for lang, xhtml in data_format.getSubDict("xhtml", data["extra"]):
                 self._check_body_text(data, lang, xhtml, self._s.SYNTAX_XHTML, defers)
                 d = self._s.convert(xhtml, self._s.SYNTAX_XHTML, self.SYNTAX_XHTML_IM)
                 d.addCallback(syntax_converted, lang)
                 defers.append(d)
-        elif u'rich' in data['extra']:
+        elif u"rich" in data["extra"]:
             # we have rich syntax to convert
-            for lang, rich_data in data_format.getSubDict('rich', data['extra']):
+            for lang, rich_data in data_format.getSubDict("rich", data["extra"]):
                 self._check_body_text(data, lang, rich_data, syntax, defers)
                 d = self._s.convert(rich_data, syntax, self.SYNTAX_XHTML_IM)
                 d.addCallback(syntax_converted, lang)
@@ -174,12 +200,12 @@
         """ Check presence of XHTML-IM in message
         """
         try:
-            html_elt = message.elements(NS_XHTML_IM, 'html').next()
+            html_elt = message.elements(NS_XHTML_IM, "html").next()
         except StopIteration:
             # No XHTML-IM
             pass
         else:
-            body_elts = html_elt.elements(NS_XHTML, 'body')
+            body_elts = html_elt.elements(NS_XHTML, "body")
             post_treat.addCallback(self._messagePostTreat, message, body_elts, client)
         return True
 
@@ -187,18 +213,20 @@
         """ Check presence of rich text in extra """
         rich = {}
         xhtml = {}
-        for key, value in data['extra'].iteritems():
-            if key.startswith('rich'):
+        for key, value in data["extra"].iteritems():
+            if key.startswith("rich"):
                 rich[key[5:]] = value
-            elif key.startswith('xhtml'):
+            elif key.startswith("xhtml"):
                 xhtml[key[6:]] = value
         if rich and xhtml:
-            raise exceptions.DataError(_(u"Can't have XHTML and rich content at the same time"))
+            raise exceptions.DataError(
+                _(u"Can't have XHTML and rich content at the same time")
+            )
         if rich or xhtml:
             if rich:
-                data['rich'] = rich
+                data["rich"] = rich
             else:
-                data['xhtml'] = xhtml
+                data["xhtml"] = xhtml
             post_xml_treatments.addCallback(self._sendMessageAddRich, client)
         return True
 
@@ -208,7 +236,7 @@
         """
         purged = []
 
-        styles = [style.strip().split(':') for style in styles_raw.split(';')]
+        styles = [style.strip().split(":") for style in styles_raw.split(";")]
 
         for style_tuple in styles:
             if len(style_tuple) != 2:
@@ -219,7 +247,7 @@
                 continue
             purged.append((name, value.strip()))
 
-        return u'; '.join([u"%s: %s" % data for data in purged])
+        return u"; ".join([u"%s: %s" % data for data in purged])
 
     def XHTML2XHTML_IM(self, xhtml):
         """ Convert XHTML document to XHTML_IM subset
@@ -227,12 +255,12 @@
         """
         # TODO: more clever tag replacement (replace forbidden tags with equivalents when possible)
 
-        parser = html.HTMLParser(remove_comments=True, encoding='utf-8')
+        parser = html.HTMLParser(remove_comments=True, encoding="utf-8")
         root = html.fromstring(xhtml, parser=parser)
-        body_elt = root.find('body')
+        body_elt = root.find("body")
         if body_elt is None:
             # we use the whole XML as body if no body element is found
-            body_elt = html.Element('body')
+            body_elt = html.Element("body")
             body_elt.append(root)
         else:
             body_elt.attrib.clear()
@@ -247,24 +275,25 @@
                 attrib = elem.attrib
                 att_to_remove = set(attrib).difference(allowed[elem.tag])
                 for att in att_to_remove:
-                    del(attrib[att])
+                    del (attrib[att])
                 if "style" in attrib:
                     attrib["style"] = self._purgeStyle(attrib["style"])
 
         for elem in to_strip:
             if elem.tag in blacklist:
-                #we need to remove the element and all descendants
+                # we need to remove the element and all descendants
                 log.debug(u"removing black listed tag: %s" % (elem.tag))
                 elem.drop_tree()
             else:
                 elem.drop_tag()
-        if len(body_elt) !=1:
+        if len(body_elt) != 1:
             root_elt = body_elt
             body_elt.tag = "p"
         else:
             root_elt = body_elt[0]
 
-        return html.tostring(root_elt, encoding='unicode', method='xml')
+        return html.tostring(root_elt, encoding="unicode", method="xml")
+
 
 class XEP_0071_handler(XMPPHandler):
     implements(iwokkel.IDisco)
@@ -273,8 +302,8 @@
         self.plugin_parent = plugin_parent
         self.host = plugin_parent.host
 
-    def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
+    def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
         return [disco.DiscoFeature(NS_XHTML_IM)]
 
-    def getDiscoItems(self, requestor, target, nodeIdentifier=''):
+    def getDiscoItems(self, requestor, target, nodeIdentifier=""):
         return []
--- a/sat/plugins/plugin_xep_0077.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_xep_0077.py	Wed Jun 27 20:14:46 2018 +0200
@@ -21,6 +21,7 @@
 from sat.core.constants import Const as C
 from sat.core import exceptions
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from twisted.words.protocols.jabber import jid
 from twisted.words.protocols.jabber import xmlstream
@@ -29,7 +30,7 @@
 
 from wokkel import data_form
 
-NS_REG = 'jabber:iq:register'
+NS_REG = "jabber:iq:register"
 
 PLUGIN_INFO = {
     C.PI_NAME: "XEP 0077 Plugin",
@@ -38,11 +39,12 @@
     C.PI_PROTOCOLS: ["XEP-0077"],
     C.PI_DEPENDENCIES: [],
     C.PI_MAIN: "XEP_0077",
-    C.PI_DESCRIPTION: _("""Implementation of in-band registration""")
+    C.PI_DESCRIPTION: _("""Implementation of in-band registration"""),
 }
 
 # FIXME: this implementation is incomplete
 
+
 class RegisteringAuthenticator(xmlstream.ConnectAuthenticator):
     # FIXME: request IQ is not send to check available fields, while XEP recommand to use it
     # FIXME: doesn't handle data form or oob
@@ -53,8 +55,7 @@
         self.password = password
         self.email = email
         self.registered = defer.Deferred()
-        log.debug(_(u"Registration asked for {jid}").format(
-            jid = jid_))
+        log.debug(_(u"Registration asked for {jid}").format(jid=jid_))
 
     def connectionMade(self):
         log.debug(_(u"Connection made with {server}".format(server=self.jid.host)))
@@ -77,57 +78,78 @@
 
 
 class XEP_0077(object):
-
     def __init__(self, host):
         log.info(_("Plugin XEP_0077 initialization"))
         self.host = host
-        host.bridge.addMethod("inBandRegister", ".plugin", in_sign='ss', out_sign='',
-                              method=self._inBandRegister,
-                              async=True)
-        host.bridge.addMethod("inBandAccountNew", ".plugin", in_sign='ssssi', out_sign='',
-                              method=self._registerNewAccount,
-                              async=True)
-        host.bridge.addMethod("inBandUnregister", ".plugin", in_sign='ss', out_sign='',
-                              method=self._unregister,
-                              async=True)
-        host.bridge.addMethod("inBandPasswordChange", ".plugin", in_sign='ss', out_sign='',
-                              method=self._changePassword,
-                              async=True)
+        host.bridge.addMethod(
+            "inBandRegister",
+            ".plugin",
+            in_sign="ss",
+            out_sign="",
+            method=self._inBandRegister,
+            async=True,
+        )
+        host.bridge.addMethod(
+            "inBandAccountNew",
+            ".plugin",
+            in_sign="ssssi",
+            out_sign="",
+            method=self._registerNewAccount,
+            async=True,
+        )
+        host.bridge.addMethod(
+            "inBandUnregister",
+            ".plugin",
+            in_sign="ss",
+            out_sign="",
+            method=self._unregister,
+            async=True,
+        )
+        host.bridge.addMethod(
+            "inBandPasswordChange",
+            ".plugin",
+            in_sign="ss",
+            out_sign="",
+            method=self._changePassword,
+            async=True,
+        )
 
     @staticmethod
     def buildRegisterIQ(xmlstream_, jid_, password, email=None):
-        iq_elt = xmlstream.IQ(xmlstream_, 'set')
+        iq_elt = xmlstream.IQ(xmlstream_, "set")
         iq_elt["to"] = jid_.host
-        query_elt = iq_elt.addElement(('jabber:iq:register', 'query'))
-        username_elt = query_elt.addElement('username')
+        query_elt = iq_elt.addElement(("jabber:iq:register", "query"))
+        username_elt = query_elt.addElement("username")
         username_elt.addContent(jid_.user)
-        password_elt = query_elt.addElement('password')
+        password_elt = query_elt.addElement("password")
         password_elt.addContent(password)
         if email is not None:
-            email_elt = query_elt.addElement('email')
+            email_elt = query_elt.addElement("email")
             email_elt.addContent(email)
         return iq_elt
 
     def _regCb(self, answer, client, post_treat_cb):
         """Called after the first get IQ"""
         try:
-            query_elt = answer.elements(NS_REG, 'query').next()
+            query_elt = answer.elements(NS_REG, "query").next()
         except StopIteration:
             raise exceptions.DataError("Can't find expected query element")
 
         try:
-            x_elem = query_elt.elements(data_form.NS_X_DATA, 'x').next()
+            x_elem = query_elt.elements(data_form.NS_X_DATA, "x").next()
         except StopIteration:
             # XXX: it seems we have an old service which doesn't manage data forms
             log.warning(_("Can't find data form"))
-            raise exceptions.DataError(_("This gateway can't be managed by SàT, sorry :("))
+            raise exceptions.DataError(
+                _("This gateway can't be managed by SàT, sorry :(")
+            )
 
         def submitForm(data, profile):
             form_elt = xml_tools.XMLUIResultToElt(data)
 
             iq_elt = client.IQ()
-            iq_elt['id'] = answer['id']
-            iq_elt['to'] = answer['from']
+            iq_elt["id"] = answer["id"]
+            iq_elt["to"] = answer["from"]
             query_elt = iq_elt.addElement("query", NS_REG)
             query_elt.addChild(form_elt)
             d = iq_elt.send()
@@ -136,7 +158,9 @@
             return d
 
         form = data_form.Form.fromElement(x_elem)
-        submit_reg_id = self.host.registerCallback(submitForm, with_data=True, one_shot=True)
+        submit_reg_id = self.host.registerCallback(
+            submitForm, with_data=True, one_shot=True
+        )
         return xml_tools.dataForm2XMLUI(form, submit_reg_id)
 
     def _regEb(self, failure, client):
@@ -147,13 +171,15 @@
     def _regSuccess(self, answer, client, post_treat_cb):
         log.debug(_(u"registration answer: %s") % answer.toXml())
         if post_treat_cb is not None:
-            post_treat_cb(jid.JID(answer['from']), client.profile)
+            post_treat_cb(jid.JID(answer["from"]), client.profile)
         return {}
 
     def _regFailure(self, failure, client):
         log.info(_(u"Registration failure: %s") % unicode(failure.value))
-        if failure.value.condition == 'conflict':
-            raise exceptions.ConflictError( _("Username already exists, please choose an other one"))
+        if failure.value.condition == "conflict":
+            raise exceptions.ConflictError(
+                _("Username already exists, please choose an other one")
+            )
         raise failure
 
     def _inBandRegister(self, to_jid_s, profile_key=C.PROF_KEY_NONE):
@@ -167,24 +193,31 @@
         # FIXME: this post_treat_cb arguments seems wrong, check it
         client = self.host.getClient(profile_key)
         log.debug(_(u"Asking registration for {}").format(to_jid.full()))
-        reg_request = client.IQ(u'get')
+        reg_request = client.IQ(u"get")
         reg_request["from"] = client.jid.full()
         reg_request["to"] = to_jid.full()
-        reg_request.addElement('query', NS_REG)
-        d = reg_request.send(to_jid.full()).addCallbacks(self._regCb, self._regEb, callbackArgs=[client, post_treat_cb], errbackArgs=[client])
+        reg_request.addElement("query", NS_REG)
+        d = reg_request.send(to_jid.full()).addCallbacks(
+            self._regCb,
+            self._regEb,
+            callbackArgs=[client, post_treat_cb],
+            errbackArgs=[client],
+        )
         return d
 
     def _registerNewAccount(self, jid_, password, email, host, port):
         kwargs = {}
         if email:
-            kwargs['email'] = email
+            kwargs["email"] = email
         if host:
-            kwargs['host'] = host
+            kwargs["host"] = host
         if port:
-            kwargs['port'] = port
+            kwargs["port"] = port
         return self.registerNewAccount(jid.JID(jid_), password, **kwargs)
 
-    def registerNewAccount(self, jid_, password, email=None, host=u"127.0.0.1", port=C.XMPP_C2S_PORT):
+    def registerNewAccount(
+        self, jid_, password, email=None, host=u"127.0.0.1", port=C.XMPP_C2S_PORT
+    ):
         """register a new account on a XMPP server
 
         @param jid_(jid.JID): request jid to register
@@ -207,7 +240,11 @@
     def changePassword(self, client, new_password):
         iq_elt = self.buildRegisterIQ(client.xmlstream, client.jid, new_password)
         d = iq_elt.send(client.jid.host)
-        d.addCallback(lambda dummy: self.host.memory.setParam("Password", new_password, "Connection", profile_key=client.profile))
+        d.addCallback(
+            lambda dummy: self.host.memory.setParam(
+                "Password", new_password, "Connection", profile_key=client.profile
+            )
+        )
         return d
 
     def _unregister(self, to_jid_s, profile_key):
@@ -222,7 +259,7 @@
         @param to_jid(jid.JID): jid of the service or server
         """
         iq_elt = client.IQ()
-        iq_elt['to'] = to_jid.full()
-        query_elt = iq_elt.addElement((NS_REG, u'query'))
-        query_elt.addElement(u'remove')
+        iq_elt["to"] = to_jid.full()
+        query_elt = iq_elt.addElement((NS_REG, u"query"))
+        query_elt.addElement(u"remove")
         return iq_elt.send()
--- a/sat/plugins/plugin_xep_0085.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_xep_0085.py	Wed Jun 27 20:14:46 2018 +0200
@@ -21,10 +21,12 @@
 from sat.core.constants import Const as C
 from sat.core import exceptions
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from wokkel import disco, iwokkel
 from zope.interface import implements
 from twisted.words.protocols.jabber.jid import JID
+
 try:
     from twisted.words.protocols.xmlstream import XMPPHandler
 except ImportError:
@@ -50,7 +52,7 @@
     C.PI_DEPENDENCIES: [],
     C.PI_MAIN: "XEP_0085",
     C.PI_HANDLER: "yes",
-    C.PI_DESCRIPTION: _("""Implementation of Chat State Notifications Protocol""")
+    C.PI_DESCRIPTION: _("""Implementation of Chat State Notifications Protocol"""),
 }
 
 
@@ -63,7 +65,7 @@
     "inactive": {"next_state": "gone", "delay": 480},
     "gone": {"next_state": "", "delay": 0},
     "composing": {"next_state": "paused", "delay": 30},
-    "paused": {"next_state": "inactive", "delay": 450}
+    "paused": {"next_state": "inactive", "delay": 450},
 }
 
 
@@ -71,6 +73,7 @@
     """
     This error is raised when an unknown chat state is used.
     """
+
     pass
 
 
@@ -78,6 +81,7 @@
     """
     Implementation for XEP 0085
     """
+
     params = """
     <params>
     <individual>
@@ -87,10 +91,10 @@
     </individual>
     </params>
     """ % {
-        'category_name': PARAM_KEY,
-        'category_label': _(PARAM_KEY),
-        'param_name': PARAM_NAME,
-        'param_label': _('Enable chat state notifications')
+        "category_name": PARAM_KEY,
+        "category_label": _(PARAM_KEY),
+        "param_name": PARAM_NAME,
+        "param_label": _("Enable chat state notifications"),
     }
 
     def __init__(self, host):
@@ -107,11 +111,16 @@
         host.trigger.add("paramUpdateTrigger", self.paramUpdateTrigger)
 
         # args: to_s (jid as string), profile
-        host.bridge.addMethod("chatStateComposing", ".plugin", in_sign='ss',
-                              out_sign='', method=self.chatStateComposing)
+        host.bridge.addMethod(
+            "chatStateComposing",
+            ".plugin",
+            in_sign="ss",
+            out_sign="",
+            method=self.chatStateComposing,
+        )
 
         # args: from (jid as string), state in CHAT_STATES, profile
-        host.bridge.addSignal("chatStateReceived", ".plugin", signature='sss')
+        host.bridge.addSignal("chatStateReceived", ".plugin", signature="sss")
 
     def getHandler(self, client):
         return XEP_0085_handler(self, client.profile)
@@ -125,7 +134,7 @@
             # FIXME: the "unavailable" presence stanza is received by to_jid
             # before the chat state, so it will be ignored... find a way to
             # actually defer the disconnection
-            self.map[profile][to_jid]._onEvent('gone')
+            self.map[profile][to_jid]._onEvent("gone")
         del self.map[profile]
 
     def updateCache(self, entity_jid, value, profile):
@@ -139,7 +148,9 @@
         if value == DELETE_VALUE:
             self.host.memory.delEntityDatum(entity_jid, ENTITY_KEY, profile)
         else:
-            self.host.memory.updateEntityData(entity_jid, ENTITY_KEY, value, profile_key=profile)
+            self.host.memory.updateEntityData(
+                entity_jid, ENTITY_KEY, value, profile_key=profile
+            )
         if not value or value == DELETE_VALUE:
             # reinit chat state UI for this or these contact(s)
             self.host.bridge.chatStateReceived(entity_jid.full(), "", profile)
@@ -153,7 +164,9 @@
         @param type_: parameter type
         """
         if (category, name) == (PARAM_KEY, PARAM_NAME):
-            self.updateCache(C.ENTITY_ALL, True if C.bool(value) else DELETE_VALUE, profile=profile)
+            self.updateCache(
+                C.ENTITY_ALL, True if C.bool(value) else DELETE_VALUE, profile=profile
+            )
             return False
         return True
 
@@ -178,7 +191,7 @@
                     # contact enabled Chat State Notifications
                     self.updateCache(from_jid, True, profile=profile)
                 except StopIteration:
-                    if message.getAttribute('type') == 'chat':
+                    if message.getAttribute("type") == "chat":
                         # contact didn't enable Chat State Notifications
                         self.updateCache(from_jid, False, profile=profile)
                         return True
@@ -188,32 +201,40 @@
         # send our next "composing" states to any MUC and to the contacts who enabled the feature
         self._chatStateInit(from_jid, message.getAttribute("type"), profile)
 
-        state_list = [child.name for child in message.elements() if
-                      message.getAttribute("type") in MESSAGE_TYPES
-                      and child.name in CHAT_STATES
-                      and child.defaultUri == NS_CHAT_STATES]
+        state_list = [
+            child.name
+            for child in message.elements()
+            if message.getAttribute("type") in MESSAGE_TYPES
+            and child.name in CHAT_STATES
+            and child.defaultUri == NS_CHAT_STATES
+        ]
         for state in state_list:
             # there must be only one state according to the XEP
-            if state != 'gone' or message.getAttribute('type') != 'groupchat':
-                self.host.bridge.chatStateReceived(message.getAttribute("from"), state, profile)
+            if state != "gone" or message.getAttribute("type") != "groupchat":
+                self.host.bridge.chatStateReceived(
+                    message.getAttribute("from"), state, profile
+                )
             break
         return True
 
-    def sendMessageTrigger(self, client, mess_data, pre_xml_treatments, post_xml_treatments):
+    def sendMessageTrigger(
+        self, client, mess_data, pre_xml_treatments, post_xml_treatments
+    ):
         """
         Eventually add the chat state to the message and initiate
         the state machine when sending an "active" state.
         """
         profile = client.profile
+
         def treatment(mess_data):
-            message = mess_data['xml']
+            message = mess_data["xml"]
             to_jid = JID(message.getAttribute("to"))
             if not self._checkActivation(to_jid, forceEntityData=True, profile=profile):
                 return mess_data
             try:
                 # message with a body always mean active state
                 domish.generateElementsNamed(message.elements(), name="body").next()
-                message.addElement('active', NS_CHAT_STATES)
+                message.addElement("active", NS_CHAT_STATES)
                 # launch the chat state machine (init the timer)
                 if self._isMUC(to_jid, profile):
                     to_jid = to_jid.userhostJID()
@@ -236,8 +257,8 @@
         @return: bool
         """
         try:
-            type_ = self.host.memory.getEntityDatum(to_jid.userhostJID(), 'type', profile)
-            if type_ == 'chatroom': # FIXME: should not use disco instead ?
+            type_ = self.host.memory.getEntityDatum(to_jid.userhostJID(), "type", profile)
+            if type_ == "chatroom":  # FIXME: should not use disco instead ?
                 return True
         except (exceptions.UnknownEntityError, KeyError):
             pass
@@ -281,8 +302,7 @@
             return
         profile_map = self.map.setdefault(profile, {})
         if to_jid not in profile_map:
-            machine = ChatStateMachine(self.host, to_jid,
-                                       mess_type, profile)
+            machine = ChatStateMachine(self.host, to_jid, mess_type, profile)
             self.map[profile][to_jid] = machine
 
     def _chatStateActive(self, to_jid, mess_type, profile_key):
@@ -316,7 +336,9 @@
             to_jid = to_jid.userhostJID()
         elif not to_jid.resource:
             to_jid.resource = self.host.memory.getMainResource(client, to_jid)
-        if not self._checkActivation(to_jid, forceEntityData=False, profile=client.profile):
+        if not self._checkActivation(
+            to_jid, forceEntityData=False, profile=client.profile
+        ):
             return
         try:
             self.map[client.profile][to_jid]._onEvent("composing")
@@ -359,22 +381,26 @@
         assert "next_state" in transition and "delay" in transition
 
         if state != self.state and state != "active":
-            if state != 'gone' or self.mess_type != 'groupchat':
+            if state != "gone" or self.mess_type != "groupchat":
                 # send a new message without body
-                log.debug(u"sending state '{state}' to {jid}".format(state=state, jid=self.to_jid.full()))
+                log.debug(
+                    u"sending state '{state}' to {jid}".format(
+                        state=state, jid=self.to_jid.full()
+                    )
+                )
                 client = self.host.getClient(self.profile)
                 mess_data = {
-                    'from': client.jid,
-                    'to': self.to_jid,
-                    'uid': '',
-                    'message': {},
-                    'type': self.mess_type,
-                    'subject': {},
-                    'extra': {},
-                    }
+                    "from": client.jid,
+                    "to": self.to_jid,
+                    "uid": "",
+                    "message": {},
+                    "type": self.mess_type,
+                    "subject": {},
+                    "extra": {},
+                }
                 client.generateMessageXML(mess_data)
-                mess_data['xml'].addElement(state, NS_CHAT_STATES)
-                client.send(mess_data['xml'])
+                mess_data["xml"].addElement(state, NS_CHAT_STATES)
+                client.send(mess_data["xml"])
 
         self.state = state
         try:
@@ -383,7 +409,9 @@
             pass
 
         if transition["next_state"] and transition["delay"] > 0:
-            self.timer = reactor.callLater(transition["delay"], self._onEvent, transition["next_state"])
+            self.timer = reactor.callLater(
+                transition["delay"], self._onEvent, transition["next_state"]
+            )
 
 
 class XEP_0085_handler(XMPPHandler):
@@ -394,8 +422,8 @@
         self.host = plugin_parent.host
         self.profile = profile
 
-    def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
+    def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
         return [disco.DiscoFeature(NS_CHAT_STATES)]
 
-    def getDiscoItems(self, requestor, target, nodeIdentifier=''):
+    def getDiscoItems(self, requestor, target, nodeIdentifier=""):
         return []
--- a/sat/plugins/plugin_xep_0092.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_xep_0092.py	Wed Jun 27 20:14:46 2018 +0200
@@ -24,6 +24,7 @@
 from wokkel import compat
 from sat.core import exceptions
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 
 NS_VERSION = "jabber:iq:version"
@@ -37,17 +38,23 @@
     C.PI_DEPENDENCIES: [],
     C.PI_RECOMMENDATIONS: [C.TEXT_CMDS],
     C.PI_MAIN: "XEP_0092",
-    C.PI_HANDLER: "no", # version is already handler in core.xmpp module
-    C.PI_DESCRIPTION: _("""Implementation of Software Version""")
+    C.PI_HANDLER: "no",  # version is already handler in core.xmpp module
+    C.PI_DESCRIPTION: _("""Implementation of Software Version"""),
 }
 
 
 class XEP_0092(object):
-
     def __init__(self, host):
         log.info(_("Plugin XEP_0092 initialization"))
         self.host = host
-        host.bridge.addMethod("getSoftwareVersion", ".plugin", in_sign='ss', out_sign='(sss)', method=self._getVersion, async=True)
+        host.bridge.addMethod(
+            "getSoftwareVersion",
+            ".plugin",
+            in_sign="ss",
+            out_sign="(sss)",
+            method=self._getVersion,
+            async=True,
+        )
         try:
             self.host.plugins[C.TEXT_CMDS].addWhoIsCb(self._whois, 50)
         except KeyError:
@@ -56,7 +63,8 @@
     def _getVersion(self, entity_jid_s, profile_key):
         def prepareForBridge(data):
             name, version, os = data
-            return (name or '', version or '', os or '')
+            return (name or "", version or "", os or "")
+
         d = self.getVersion(jid.JID(entity_jid_s), profile_key)
         d.addCallback(prepareForBridge)
         return d
@@ -71,25 +79,29 @@
                  - os: operating system of the queried entity
         """
         client = self.host.getClient(profile_key)
+
         def getVersion(dummy):
-            iq_elt = compat.IQ(client.xmlstream, 'get')
-            iq_elt['to'] = jid_.full()
+            iq_elt = compat.IQ(client.xmlstream, "get")
+            iq_elt["to"] = jid_.full()
             iq_elt.addElement("query", NS_VERSION)
             d = iq_elt.send()
             d.addCallback(self._gotVersion)
             return d
+
         d = self.host.checkFeature(client, NS_VERSION, jid_)
         d.addCallback(getVersion)
-        reactor.callLater(TIMEOUT, d.cancel) # XXX: timeout needed because some clients don't answer the IQ
+        reactor.callLater(
+            TIMEOUT, d.cancel
+        )  # XXX: timeout needed because some clients don't answer the IQ
         return d
 
     def _gotVersion(self, iq_elt):
         try:
-            query_elt = iq_elt.elements(NS_VERSION, 'query').next()
+            query_elt = iq_elt.elements(NS_VERSION, "query").next()
         except StopIteration:
             raise exceptions.DataError
         ret = []
-        for name in ('name', 'version', 'os'):
+        for name in ("name", "version", "os"):
             try:
                 data_elt = query_elt.elements(NS_VERSION, name).next()
                 ret.append(unicode(data_elt))
@@ -98,9 +110,9 @@
 
         return tuple(ret)
 
-
     def _whois(self, client, whois_msg, mess_data, target_jid):
         """ Add software/OS information to whois """
+
         def versionCb(version_data):
             name, version, os = version_data
             if name:
@@ -109,9 +121,10 @@
                 whois_msg.append(_("Client version: %s") % version)
             if os:
                 whois_msg.append(_("Operating system: %s") % os)
+
         def versionEb(failure):
             failure.trap(exceptions.FeatureNotFound, defer.CancelledError)
-            if failure.check(failure,exceptions.FeatureNotFound):
+            if failure.check(failure, exceptions.FeatureNotFound):
                 whois_msg.append(_("Software version not available"))
             else:
                 whois_msg.append(_("Client software version request timeout"))
@@ -119,4 +132,3 @@
         d = self.getVersion(target_jid, client.profile)
         d.addCallbacks(versionCb, versionEb)
         return d
-
--- a/sat/plugins/plugin_xep_0095.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_xep_0095.py	Wed Jun 27 20:14:46 2018 +0200
@@ -20,6 +20,7 @@
 from sat.core.i18n import _
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from sat.core import exceptions
 from twisted.words.protocols.jabber import xmlstream
@@ -37,19 +38,18 @@
     C.PI_PROTOCOLS: ["XEP-0095"],
     C.PI_MAIN: "XEP_0095",
     C.PI_HANDLER: "yes",
-    C.PI_DESCRIPTION: _("""Implementation of Stream Initiation""")
+    C.PI_DESCRIPTION: _("""Implementation of Stream Initiation"""),
 }
 
 
 IQ_SET = '/iq[@type="set"]'
-NS_SI = 'http://jabber.org/protocol/si'
+NS_SI = "http://jabber.org/protocol/si"
 SI_REQUEST = IQ_SET + '/si[@xmlns="' + NS_SI + '"]'
 SI_PROFILE_HEADER = "http://jabber.org/protocol/si/profile/"
-SI_ERROR_CONDITIONS = ('bad-profile', 'no-valid-streams')
+SI_ERROR_CONDITIONS = ("bad-profile", "no-valid-streams")
 
 
 class XEP_0095(object):
-
     def __init__(self, host):
         log.info(_("Plugin XEP_0095 initialization"))
         self.host = host
@@ -70,7 +70,11 @@
         try:
             del self.si_profiles[si_profile]
         except KeyError:
-            log.error(u"Trying to unregister SI profile [{}] which was not registered".format(si_profile))
+            log.error(
+                u"Trying to unregister SI profile [{}] which was not registered".format(
+                    si_profile
+                )
+            )
 
     def streamInit(self, iq_elt, client):
         """This method is called on stream initiation (XEP-0095 #3.2)
@@ -79,17 +83,21 @@
         """
         log.info(_("XEP-0095 Stream initiation"))
         iq_elt.handled = True
-        si_elt = iq_elt.elements(NS_SI, 'si').next()
-        si_id = si_elt['id']
-        si_mime_type = iq_elt.getAttribute('mime-type', 'application/octet-stream')
-        si_profile = si_elt['profile']
-        si_profile_key = si_profile[len(SI_PROFILE_HEADER):] if si_profile.startswith(SI_PROFILE_HEADER) else si_profile
+        si_elt = iq_elt.elements(NS_SI, "si").next()
+        si_id = si_elt["id"]
+        si_mime_type = iq_elt.getAttribute("mime-type", "application/octet-stream")
+        si_profile = si_elt["profile"]
+        si_profile_key = (
+            si_profile[len(SI_PROFILE_HEADER) :]
+            if si_profile.startswith(SI_PROFILE_HEADER)
+            else si_profile
+        )
         if si_profile_key in self.si_profiles:
-            #We know this SI profile, we call the callback
+            # We know this SI profile, we call the callback
             self.si_profiles[si_profile_key](client, iq_elt, si_id, si_mime_type, si_elt)
         else:
-            #We don't know this profile, we send an error
-            self.sendError(client, iq_elt, 'bad-profile')
+            # We don't know this profile, we send an error
+            self.sendError(client, iq_elt, "bad-profile")
 
     def sendError(self, client, request, condition):
         """Send IQ error as a result
@@ -99,7 +107,7 @@
         """
         if condition in SI_ERROR_CONDITIONS:
             si_condition = condition
-            condition = 'bad-request'
+            condition = "bad-request"
         else:
             si_condition = None
 
@@ -119,8 +127,8 @@
         log.info(_("sending stream initiation accept answer"))
         if misc_elts is None:
             misc_elts = []
-        result_elt = xmlstream.toResponse(iq_elt, 'result')
-        si_elt = result_elt.addElement((NS_SI, 'si'))
+        result_elt = xmlstream.toResponse(iq_elt, "result")
+        si_elt = result_elt.addElement((NS_SI, "si"))
         si_elt.addChild(feature_elt)
         for elt in misc_elts:
             si_elt.addChild(elt)
@@ -134,8 +142,15 @@
             raise exceptions.DataError
         return (iq_elt, si_elt)
 
-
-    def proposeStream(self, client, to_jid, si_profile, feature_elt, misc_elts, mime_type='application/octet-stream'):
+    def proposeStream(
+        self,
+        client,
+        to_jid,
+        si_profile,
+        feature_elt,
+        misc_elts,
+        mime_type="application/octet-stream",
+    ):
         """Propose a stream initiation
 
         @param to_jid(jid.JID): recipient
@@ -154,8 +169,8 @@
 
         offer["from"] = client.jid.full()
         offer["to"] = to_jid.full()
-        si = offer.addElement('si', NS_SI)
-        si['id'] = sid
+        si = offer.addElement("si", NS_SI)
+        si["id"] = sid
         si["mime-type"] = mime_type
         si["profile"] = si_profile
         for elt in misc_elts:
@@ -175,10 +190,17 @@
         self.host = plugin_parent.host
 
     def connectionInitialized(self):
-        self.xmlstream.addObserver(SI_REQUEST, self.plugin_parent.streamInit, client=self.parent)
+        self.xmlstream.addObserver(
+            SI_REQUEST, self.plugin_parent.streamInit, client=self.parent
+        )
 
-    def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
-        return [disco.DiscoFeature(NS_SI)] + [disco.DiscoFeature(u"http://jabber.org/protocol/si/profile/{}".format(profile_name)) for profile_name in self.plugin_parent.si_profiles]
+    def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
+        return [disco.DiscoFeature(NS_SI)] + [
+            disco.DiscoFeature(
+                u"http://jabber.org/protocol/si/profile/{}".format(profile_name)
+            )
+            for profile_name in self.plugin_parent.si_profiles
+        ]
 
-    def getDiscoItems(self, requestor, target, nodeIdentifier=''):
+    def getDiscoItems(self, requestor, target, nodeIdentifier=""):
         return []
--- a/sat/plugins/plugin_xep_0096.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_xep_0096.py	Wed Jun 27 20:14:46 2018 +0200
@@ -20,6 +20,7 @@
 from sat.core.i18n import _, D_
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from sat.core import exceptions
 from sat.tools import xml_tools
@@ -43,7 +44,7 @@
     C.PI_DEPENDENCIES: ["XEP-0020", "XEP-0095", "XEP-0065", "XEP-0047", "FILE"],
     C.PI_MAIN: "XEP_0096",
     C.PI_HANDLER: "no",
-    C.PI_DESCRIPTION: _("""Implementation of SI File Transfer""")
+    C.PI_DESCRIPTION: _("""Implementation of SI File Transfer"""),
 }
 
 
@@ -53,13 +54,19 @@
     def __init__(self, host):
         log.info(_("Plugin XEP_0096 initialization"))
         self.host = host
-        self.managed_stream_m = [self.host.plugins["XEP-0065"].NAMESPACE,
-                                 self.host.plugins["XEP-0047"].NAMESPACE]  # Stream methods managed
+        self.managed_stream_m = [
+            self.host.plugins["XEP-0065"].NAMESPACE,
+            self.host.plugins["XEP-0047"].NAMESPACE,
+        ]  # Stream methods managed
         self._f = self.host.plugins["FILE"]
-        self._f.register(NS_SI_FT, self.sendFile, priority=0, method_name=u"Stream Initiation")
+        self._f.register(
+            NS_SI_FT, self.sendFile, priority=0, method_name=u"Stream Initiation"
+        )
         self._si = self.host.plugins["XEP-0095"]
         self._si.registerSIProfile(SI_PROFILE_NAME, self._transferRequest)
-        host.bridge.addMethod("siSendFile", ".plugin", in_sign='sssss', out_sign='s', method=self._sendFile)
+        host.bridge.addMethod(
+            "siSendFile", ".plugin", in_sign="sssss", out_sign="s", method=self._sendFile
+        )
 
     def unload(self):
         self._si.unregisterSIProfile(SI_PROFILE_NAME)
@@ -72,7 +79,7 @@
         """
         if message is not None:
             log.warning(message)
-        self._si.sendError(client, iq_elt, 'bad-request')
+        self._si.sendError(client, iq_elt, "bad-request")
 
     def _parseRange(self, parent_elt, file_size):
         """find and parse <range/> element
@@ -84,7 +91,7 @@
             - range_length
         """
         try:
-            range_elt = parent_elt.elements(NS_SI_FT, 'range').next()
+            range_elt = parent_elt.elements(NS_SI_FT, "range").next()
         except StopIteration:
             range_ = False
             range_offset = None
@@ -93,17 +100,17 @@
             range_ = True
 
             try:
-                range_offset = int(range_elt['offset'])
+                range_offset = int(range_elt["offset"])
             except KeyError:
                 range_offset = 0
 
             try:
-                range_length = int(range_elt['length'])
+                range_length = int(range_elt["length"])
             except KeyError:
                 range_length = file_size
 
             if range_offset != 0 or range_length != file_size:
-                raise NotImplementedError # FIXME
+                raise NotImplementedError  # FIXME
 
         return range_, range_offset, range_length
 
@@ -116,17 +123,21 @@
         @param si_elt(domish.Element): request
         """
         log.info(_("XEP-0096 file transfer requested"))
-        peer_jid = jid.JID(iq_elt['from'])
+        peer_jid = jid.JID(iq_elt["from"])
 
         try:
             file_elt = si_elt.elements(NS_SI_FT, "file").next()
         except StopIteration:
-            return self._badRequest(client, iq_elt, "No <file/> element found in SI File Transfer request")
+            return self._badRequest(
+                client, iq_elt, "No <file/> element found in SI File Transfer request"
+            )
 
         try:
             feature_elt = self.host.plugins["XEP-0020"].getFeatureElt(si_elt)
         except exceptions.NotFound:
-            return self._badRequest(client, iq_elt, "No <feature/> element found in SI File Transfer request")
+            return self._badRequest(
+                client, iq_elt, "No <feature/> element found in SI File Transfer request"
+            )
 
         try:
             filename = file_elt["name"]
@@ -137,12 +148,16 @@
         file_date = file_elt.getAttribute("date")
         file_hash = file_elt.getAttribute("hash")
 
-        log.info(u"File proposed: name=[{name}] size={size}".format(name=filename, size=file_size))
+        log.info(
+            u"File proposed: name=[{name}] size={size}".format(
+                name=filename, size=file_size
+            )
+        )
 
         try:
-            file_desc = unicode(file_elt.elements(NS_SI_FT, 'desc').next())
+            file_desc = unicode(file_elt.elements(NS_SI_FT, "desc").next())
         except StopIteration:
-            file_desc = ''
+            file_desc = ""
 
         try:
             range_, range_offset, range_length = self._parseRange(file_elt, file_size)
@@ -150,7 +165,9 @@
             return self._badRequest(client, iq_elt, "Malformed SI File Transfer request")
 
         try:
-            stream_method = self.host.plugins["XEP-0020"].negotiate(feature_elt, 'stream-method', self.managed_stream_m, namespace=None)
+            stream_method = self.host.plugins["XEP-0020"].negotiate(
+                feature_elt, "stream-method", self.managed_stream_m, namespace=None
+            )
         except KeyError:
             return self._badRequest(client, iq_elt, "No stream method found")
 
@@ -160,16 +177,30 @@
             elif stream_method == self.host.plugins["XEP-0047"].NAMESPACE:
                 plugin = self.host.plugins["XEP-0047"]
             else:
-                log.error(u"Unknown stream method, this should not happen at this stage, cancelling transfer")
+                log.error(
+                    u"Unknown stream method, this should not happen at this stage, cancelling transfer"
+                )
         else:
             log.warning(u"Can't find a valid stream method")
-            self._si.sendError(client, iq_elt, 'not-acceptable')
+            self._si.sendError(client, iq_elt, "not-acceptable")
             return
 
-        #if we are here, the transfer can start, we just need user's agreement
-        data = {"name": filename, "peer_jid": peer_jid, "size": file_size, "date": file_date, "hash": file_hash, "desc": file_desc,
-                "range": range_, "range_offset": range_offset, "range_length": range_length,
-                "si_id": si_id, "progress_id": si_id, "stream_method": stream_method, "stream_plugin": plugin}
+        # if we are here, the transfer can start, we just need user's agreement
+        data = {
+            "name": filename,
+            "peer_jid": peer_jid,
+            "size": file_size,
+            "date": file_date,
+            "hash": file_hash,
+            "desc": file_desc,
+            "range": range_,
+            "range_offset": range_offset,
+            "range_length": range_length,
+            "si_id": si_id,
+            "progress_id": si_id,
+            "stream_method": stream_method,
+            "stream_plugin": plugin,
+        }
 
         d = self._f.getDestDir(client, peer_jid, data, data, stream_object=True)
         d.addCallback(self.confirmationCb, client, iq_elt, data)
@@ -183,7 +214,7 @@
         """
         if not accepted:
             log.info(u"File transfer declined")
-            self._si.sendError(client, iq_elt, 'forbidden')
+            self._si.sendError(client, iq_elt, "forbidden")
             return
         # data, timeout, stream_method, failed_methods = client._xep_0096_waiting_for_approval[sid]
         # can_range = data['can_range'] == "True"
@@ -207,12 +238,16 @@
 
         # file_obj = self._getFileObject(dest_path, can_range)
         # range_offset = file_obj.tell()
-        d = data['stream_plugin'].createSession(client, data['stream_object'], data['peer_jid'], data['si_id'])
+        d = data["stream_plugin"].createSession(
+            client, data["stream_object"], data["peer_jid"], data["si_id"]
+        )
         d.addCallback(self._transferCb, client, data)
         d.addErrback(self._transferEb, client, data)
 
-        #we can send the iq result
-        feature_elt = self.host.plugins["XEP-0020"].chooseOption({'stream-method': data['stream_method']}, namespace=None)
+        # we can send the iq result
+        feature_elt = self.host.plugins["XEP-0020"].chooseOption(
+            {"stream-method": data["stream_method"]}, namespace=None
+        )
         misc_elts = []
         misc_elts.append(domish.Element((SI_PROFILE, "file")))
         # if can_range:
@@ -227,9 +262,9 @@
 
         @param data: session data
         """
-        #TODO: check hash
-        data['stream_object'].close()
-        log.info(u'Transfer {si_id} successfuly finished'.format(**data))
+        # TODO: check hash
+        data["stream_object"].close()
+        log.info(u"Transfer {si_id} successfuly finished".format(**data))
 
     def _transferEb(self, failure, client, data):
         """Called when something went wrong with the transfer
@@ -237,12 +272,18 @@
         @param id: stream id
         @param data: session data
         """
-        log.warning(u'Transfer {si_id} failed: {reason}'.format(reason=unicode(failure.value), **data))
-        data['stream_object'].close()
+        log.warning(
+            u"Transfer {si_id} failed: {reason}".format(
+                reason=unicode(failure.value), **data
+            )
+        )
+        data["stream_object"].close()
 
     def _sendFile(self, peer_jid_s, filepath, name, desc, profile=C.PROF_KEY_NONE):
         client = self.host.getClient(profile)
-        return self.sendFile(client, jid.JID(peer_jid_s), filepath, name or None, desc or None)
+        return self.sendFile(
+            client, jid.JID(peer_jid_s), filepath, name or None, desc or None
+        )
 
     def sendFile(self, client, peer_jid, filepath, name=None, desc=None, extra=None):
         """Send a file using XEP-0096
@@ -255,23 +296,27 @@
         @param extra: not used here
         @return: an unique id to identify the transfer
         """
-        feature_elt = self.host.plugins["XEP-0020"].proposeFeatures({'stream-method': self.managed_stream_m}, namespace=None)
+        feature_elt = self.host.plugins["XEP-0020"].proposeFeatures(
+            {"stream-method": self.managed_stream_m}, namespace=None
+        )
 
         file_transfer_elts = []
 
         statinfo = os.stat(filepath)
-        file_elt = domish.Element((SI_PROFILE, 'file'))
-        file_elt['name'] = name or os.path.basename(filepath)
-        assert '/' not in file_elt['name']
+        file_elt = domish.Element((SI_PROFILE, "file"))
+        file_elt["name"] = name or os.path.basename(filepath)
+        assert "/" not in file_elt["name"]
         size = statinfo.st_size
-        file_elt['size'] = str(size)
+        file_elt["size"] = str(size)
         if desc:
-            file_elt.addElement('desc', content=desc)
+            file_elt.addElement("desc", content=desc)
         file_transfer_elts.append(file_elt)
 
-        file_transfer_elts.append(domish.Element((None, 'range')))
+        file_transfer_elts.append(domish.Element((None, "range")))
 
-        sid, offer_d = self._si.proposeStream(client, peer_jid, SI_PROFILE, feature_elt, file_transfer_elts)
+        sid, offer_d = self._si.proposeStream(
+            client, peer_jid, SI_PROFILE, feature_elt, file_transfer_elts
+        )
         args = [filepath, sid, size, client]
         offer_d.addCallbacks(self._fileCb, self._fileEb, args, None, args)
         return sid
@@ -285,7 +330,9 @@
             log.warning(u"No <feature/> element found in result while expected")
             return
 
-        choosed_options = self.host.plugins["XEP-0020"].getChoosedOptions(feature_elt, namespace=None)
+        choosed_options = self.host.plugins["XEP-0020"].getChoosedOptions(
+            feature_elt, namespace=None
+        )
         try:
             stream_method = choosed_options["stream-method"]
         except KeyError:
@@ -307,45 +354,46 @@
             log.warning(u"Invalid stream method received")
             return
 
-        stream_object = stream.FileStreamObject(self.host,
-                                                client,
-                                                filepath,
-                                                uid=sid,
-                                                size=size,
-                                                )
-        d = plugin.startStream(client, stream_object, jid.JID(iq_elt['from']), sid)
+        stream_object = stream.FileStreamObject(
+            self.host, client, filepath, uid=sid, size=size
+        )
+        d = plugin.startStream(client, stream_object, jid.JID(iq_elt["from"]), sid)
         d.addCallback(self._sendCb, client, sid, stream_object)
         d.addErrback(self._sendEb, client, sid, stream_object)
 
     def _fileEb(self, failure, filepath, sid, size, client):
         if failure.check(error.StanzaError):
             stanza_err = failure.value
-            if stanza_err.code == '403' and stanza_err.condition == 'forbidden':
-                from_s = stanza_err.stanza['from']
+            if stanza_err.code == "403" and stanza_err.condition == "forbidden":
+                from_s = stanza_err.stanza["from"]
                 log.info(u"File transfer refused by {}".format(from_s))
                 msg = D_(u"The contact {} has refused your file").format(from_s)
                 title = D_(u"File refused")
                 xml_tools.quickNote(self.host, client, msg, title, C.XMLUI_DATA_LVL_INFO)
             else:
                 log.warning(_(u"Error during file transfer"))
-                msg = D_(u"Something went wrong during the file transfer session initialisation: {reason}").format(reason=unicode(stanza_err))
+                msg = D_(
+                    u"Something went wrong during the file transfer session initialisation: {reason}"
+                ).format(reason=unicode(stanza_err))
                 title = D_(u"File transfer error")
                 xml_tools.quickNote(self.host, client, msg, title, C.XMLUI_DATA_LVL_ERROR)
         elif failure.check(exceptions.DataError):
-            log.warning(u'Invalid stanza received')
+            log.warning(u"Invalid stanza received")
         else:
-            log.error(u'Error while proposing stream: {}'.format(failure))
+            log.error(u"Error while proposing stream: {}".format(failure))
 
     def _sendCb(self, dummy, client, sid, stream_object):
-        log.info(_(u'transfer {sid} successfuly finished [{profile}]').format(
-            sid=sid,
-            profile=client.profile))
+        log.info(
+            _(u"transfer {sid} successfuly finished [{profile}]").format(
+                sid=sid, profile=client.profile
+            )
+        )
         stream_object.close()
 
     def _sendEb(self, failure, client, sid, stream_object):
-        log.warning(_(u'transfer {sid} failed [{profile}]: {reason}').format(
-            sid=sid,
-            profile=client.profile,
-            reason=unicode(failure.value),
-            ))
+        log.warning(
+            _(u"transfer {sid} failed [{profile}]: {reason}").format(
+                sid=sid, profile=client.profile, reason=unicode(failure.value)
+            )
+        )
         stream_object.close()
--- a/sat/plugins/plugin_xep_0100.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_xep_0100.py	Wed Jun 27 20:14:46 2018 +0200
@@ -22,6 +22,7 @@
 from sat.core import exceptions
 from sat.tools import xml_tools
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from twisted.words.protocols.jabber import jid
 from twisted.internet import reactor, defer
@@ -33,37 +34,58 @@
     C.PI_PROTOCOLS: ["XEP-0100"],
     C.PI_DEPENDENCIES: ["XEP-0077"],
     C.PI_MAIN: "XEP_0100",
-    C.PI_DESCRIPTION: _("""Implementation of Gateways protocol""")
+    C.PI_DESCRIPTION: _("""Implementation of Gateways protocol"""),
 }
 
-WARNING_MSG = D_(u"""Be careful ! Gateways allow you to use an external IM (legacy IM), so you can see your contact as XMPP contacts.
-But when you do this, all your messages go throught the external legacy IM server, it is a huge privacy issue (i.e.: all your messages throught the gateway can be monitored, recorded, analysed by the external server, most of time a private company).""")
+WARNING_MSG = D_(
+    u"""Be careful ! Gateways allow you to use an external IM (legacy IM), so you can see your contact as XMPP contacts.
+But when you do this, all your messages go throught the external legacy IM server, it is a huge privacy issue (i.e.: all your messages throught the gateway can be monitored, recorded, analysed by the external server, most of time a private company)."""
+)
 
 GATEWAY_TIMEOUT = 10  # time to wait before cancelling a gateway disco info, in seconds
 
-TYPE_DESCRIPTIONS = {'irc': D_("Internet Relay Chat"),
-                     'xmpp': D_("XMPP"),
-                     'qq': D_("Tencent QQ"),
-                     'simple': D_("SIP/SIMPLE"),
-                     'icq': D_("ICQ"),
-                     'yahoo': D_("Yahoo! Messenger"),
-                     'gadu-gadu': D_("Gadu-Gadu"),
-                     'aim': D_("AOL Instant Messenger"),
-                     'msn': D_("Windows Live Messenger"),
-                    }
+TYPE_DESCRIPTIONS = {
+    "irc": D_("Internet Relay Chat"),
+    "xmpp": D_("XMPP"),
+    "qq": D_("Tencent QQ"),
+    "simple": D_("SIP/SIMPLE"),
+    "icq": D_("ICQ"),
+    "yahoo": D_("Yahoo! Messenger"),
+    "gadu-gadu": D_("Gadu-Gadu"),
+    "aim": D_("AOL Instant Messenger"),
+    "msn": D_("Windows Live Messenger"),
+}
 
 
 class XEP_0100(object):
-
     def __init__(self, host):
         log.info(_("Gateways plugin initialization"))
         self.host = host
         self.__gateways = {}  # dict used to construct the answer to findGateways. Key = target jid
-        host.bridge.addMethod("findGateways", ".plugin", in_sign='ss', out_sign='s', method=self._findGateways)
-        host.bridge.addMethod("gatewayRegister", ".plugin", in_sign='ss', out_sign='s', method=self._gatewayRegister)
+        host.bridge.addMethod(
+            "findGateways",
+            ".plugin",
+            in_sign="ss",
+            out_sign="s",
+            method=self._findGateways,
+        )
+        host.bridge.addMethod(
+            "gatewayRegister",
+            ".plugin",
+            in_sign="ss",
+            out_sign="s",
+            method=self._gatewayRegister,
+        )
         self.__menu_id = host.registerCallback(self._gatewaysMenu, with_data=True)
-        self.__selected_id = host.registerCallback(self._gatewaySelectedCb, with_data=True)
-        host.importMenu((D_("Service"), D_("Gateways")), self._gatewaysMenu, security_limit=1, help_string=D_("Find gateways"))
+        self.__selected_id = host.registerCallback(
+            self._gatewaySelectedCb, with_data=True
+        )
+        host.importMenu(
+            (D_("Service"), D_("Gateways")),
+            self._gatewaysMenu,
+            security_limit=1,
+            help_string=D_("Find gateways"),
+        )
 
     def _gatewaysMenu(self, data, profile):
         """ XMLUI activated by menu: return Gateways UI
@@ -72,24 +94,31 @@
         """
         client = self.host.getClient(profile)
         try:
-            jid_ = jid.JID(data.get(xml_tools.formEscape('external_jid'), client.jid.host))
+            jid_ = jid.JID(
+                data.get(xml_tools.formEscape("external_jid"), client.jid.host)
+            )
         except RuntimeError:
             raise exceptions.DataError(_("Invalid JID"))
         d = self.findGateways(jid_, profile)
         d.addCallback(self._gatewaysResult2XMLUI, jid_)
-        d.addCallback(lambda xmlui: {'xmlui': xmlui.toXml()})
+        d.addCallback(lambda xmlui: {"xmlui": xmlui.toXml()})
         return d
 
     def _gatewaysResult2XMLUI(self, result, entity):
-        xmlui = xml_tools.XMLUI(title=_('Gateways manager (%s)') % entity.full())
+        xmlui = xml_tools.XMLUI(title=_("Gateways manager (%s)") % entity.full())
         xmlui.addText(_(WARNING_MSG))
-        xmlui.addDivider('dash')
-        adv_list = xmlui.changeContainer('advanced_list', columns=3, selectable='single', callback_id=self.__selected_id)
+        xmlui.addDivider("dash")
+        adv_list = xmlui.changeContainer(
+            "advanced_list",
+            columns=3,
+            selectable="single",
+            callback_id=self.__selected_id,
+        )
         for success, gateway_data in result:
             if not success:
                 fail_cond, disco_item = gateway_data
                 xmlui.addJid(disco_item.entity)
-                xmlui.addText(_('Failed (%s)') % fail_cond)
+                xmlui.addText(_("Failed (%s)") % fail_cond)
                 xmlui.addEmpty()
             else:
                 jid_, data = gateway_data
@@ -100,22 +129,22 @@
                     xmlui.addText(name)
                     xmlui.addText(self._getIdentityDesc(identity))
         adv_list.end()
-        xmlui.addDivider('blank')
-        xmlui.changeContainer('advanced_list', columns=3)
-        xmlui.addLabel(_('Use external XMPP server'))
-        xmlui.addString('external_jid')
-        xmlui.addButton(self.__menu_id, _(u'Go !'), fields_back=('external_jid',))
+        xmlui.addDivider("blank")
+        xmlui.changeContainer("advanced_list", columns=3)
+        xmlui.addLabel(_("Use external XMPP server"))
+        xmlui.addString("external_jid")
+        xmlui.addButton(self.__menu_id, _(u"Go !"), fields_back=("external_jid",))
         return xmlui
 
     def _gatewaySelectedCb(self, data, profile):
         try:
-            target_jid = jid.JID(data['index'])
+            target_jid = jid.JID(data["index"])
         except (KeyError, RuntimeError):
             log.warning(_("No gateway index selected"))
             return {}
 
         d = self.gatewayRegister(target_jid, profile)
-        d.addCallback(lambda xmlui: {'xmlui': xmlui.toXml()})
+        d.addCallback(lambda xmlui: {"xmlui": xmlui.toXml()})
         return d
 
     def _getIdentityDesc(self, identity):
@@ -124,8 +153,13 @@
 
         """
         category, type_ = identity
-        if category != 'gateway':
-            log.error(_(u'INTERNAL ERROR: identity category should always be "gateway" in _getTypeString, got "%s"') % category)
+        if category != "gateway":
+            log.error(
+                _(
+                    u'INTERNAL ERROR: identity category should always be "gateway" in _getTypeString, got "%s"'
+                )
+                % category
+            )
         try:
             return _(TYPE_DESCRIPTIONS[type_])
         except KeyError:
@@ -145,8 +179,10 @@
     def gatewayRegister(self, target_jid, profile_key=C.PROF_KEY_NONE):
         """Register gateway using in-band registration, then log-in to gateway"""
         profile = self.host.memory.getProfileName(profile_key)
-        assert(profile)
-        d = self.host.plugins["XEP-0077"].inBandRegister(target_jid, self._registrationSuccessful, profile)
+        assert profile
+        d = self.host.plugins["XEP-0077"].inBandRegister(
+            target_jid, self._registrationSuccessful, profile
+        )
         return d
 
     def _infosReceived(self, dl_result, items, target, client):
@@ -165,12 +201,27 @@
                 ret.append((success, (msg, items[idx])))
             else:
                 entity = items[idx].entity
-                gateways = [(identity, result.identities[identity]) for identity in result.identities if identity[0] == 'gateway']
+                gateways = [
+                    (identity, result.identities[identity])
+                    for identity in result.identities
+                    if identity[0] == "gateway"
+                ]
                 if gateways:
-                    log.info(_(u"Found gateway [%(jid)s]: %(identity_name)s") % {'jid': entity.full(), 'identity_name': ' - '.join([gateway[1] for gateway in gateways])})
+                    log.info(
+                        _(u"Found gateway [%(jid)s]: %(identity_name)s")
+                        % {
+                            "jid": entity.full(),
+                            "identity_name": " - ".join(
+                                [gateway[1] for gateway in gateways]
+                            ),
+                        }
+                    )
                     ret.append((success, (entity, gateways)))
                 else:
-                    log.info(_(u"Skipping [%(jid)s] which is not a gateway") % {'jid': entity.full()})
+                    log.info(
+                        _(u"Skipping [%(jid)s] which is not a gateway")
+                        % {"jid": entity.full()}
+                    )
         return ret
 
     def _itemsReceived(self, disco, target, client):
@@ -185,7 +236,9 @@
             log.debug(_(u"item found: %s") % item.entity)
             _defers.append(client.disco.requestInfo(item.entity))
         dl = defer.DeferredList(_defers)
-        dl.addCallback(self._infosReceived, items=disco._items, target=target, client=client)
+        dl.addCallback(
+            self._infosReceived, items=disco._items, target=target, client=client
+        )
         reactor.callLater(GATEWAY_TIMEOUT, dl.cancel)
         return dl
 
@@ -203,7 +256,10 @@
         """Find gateways in the target JID, using discovery protocol
         """
         client = self.host.getClient(profile)
-        log.debug(_(u"find gateways (target = %(target)s, profile = %(profile)s)") % {'target': target.full(), 'profile': profile})
+        log.debug(
+            _(u"find gateways (target = %(target)s, profile = %(profile)s)")
+            % {"target": target.full(), "profile": profile}
+        )
         d = client.disco.requestItems(target)
         d.addCallback(self._itemsReceived, target=target, client=client)
         return d
--- a/sat/plugins/plugin_xep_0115.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_xep_0115.py	Wed Jun 27 20:14:46 2018 +0200
@@ -20,6 +20,7 @@
 from sat.core.i18n import _
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from twisted.words.xish import domish
 from twisted.words.protocols.jabber import jid
@@ -32,9 +33,9 @@
 except ImportError:
     from wokkel.subprotocols import XMPPHandler
 
-PRESENCE = '/presence'
-NS_ENTITY_CAPABILITY = 'http://jabber.org/protocol/caps'
-NS_CAPS_OPTIMIZE = 'http://jabber.org/protocol/caps#optimize'
+PRESENCE = "/presence"
+NS_ENTITY_CAPABILITY = "http://jabber.org/protocol/caps"
+NS_CAPS_OPTIMIZE = "http://jabber.org/protocol/caps#optimize"
 CAPABILITY_UPDATE = PRESENCE + '/c[@xmlns="' + NS_ENTITY_CAPABILITY + '"]'
 
 PLUGIN_INFO = {
@@ -46,7 +47,7 @@
     C.PI_DEPENDENCIES: [],
     C.PI_MAIN: "XEP_0115",
     C.PI_HANDLER: "yes",
-    C.PI_DESCRIPTION: _("""Implementation of entity capabilities""")
+    C.PI_DESCRIPTION: _("""Implementation of entity capabilities"""),
 }
 
 
@@ -75,24 +76,29 @@
             log.warning(_(u"Caps optimisation not available"))
 
         # hash generation
-        _infos = yield client.discoHandler.info(client.jid, client.jid, '')
+        _infos = yield client.discoHandler.info(client.jid, client.jid, "")
         disco_infos = disco.DiscoInfo()
         for item in _infos:
             disco_infos.append(item)
         cap_hash = client._caps_hash = self.host.memory.disco.generateHash(disco_infos)
-        log.info(u"Our capability hash has been generated: [{cap_hash}]".format(
-            cap_hash = cap_hash))
+        log.info(
+            u"Our capability hash has been generated: [{cap_hash}]".format(
+                cap_hash=cap_hash
+            )
+        )
         log.debug(u"Generating capability domish.Element")
-        c_elt = domish.Element((NS_ENTITY_CAPABILITY, 'c'))
-        c_elt['hash'] = 'sha-1'
-        c_elt['node'] = C.APP_URL
-        c_elt['ver'] = cap_hash
+        c_elt = domish.Element((NS_ENTITY_CAPABILITY, "c"))
+        c_elt["hash"] = "sha-1"
+        c_elt["node"] = C.APP_URL
+        c_elt["ver"] = cap_hash
         client._caps_elt = c_elt
         if client._caps_optimize:
             client._caps_sent = False
         if cap_hash not in self.host.memory.disco.hashes:
             self.host.memory.disco.hashes[cap_hash] = disco_infos
-            self.host.memory.updateEntityData(client.jid, C.ENTITY_CAP_HASH, cap_hash, profile_key=client.profile)
+            self.host.memory.updateEntityData(
+                client.jid, C.ENTITY_CAP_HASH, cap_hash, profile_key=client.profile
+            )
 
     def _presenceAddElt(self, client, obj):
         if client._caps_optimize:
@@ -120,10 +126,13 @@
     def connectionInitialized(self):
         self.xmlstream.addObserver(CAPABILITY_UPDATE, self.update)
 
-    def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
-        return [disco.DiscoFeature(NS_ENTITY_CAPABILITY), disco.DiscoFeature(NS_CAPS_OPTIMIZE)]
+    def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
+        return [
+            disco.DiscoFeature(NS_ENTITY_CAPABILITY),
+            disco.DiscoFeature(NS_CAPS_OPTIMIZE),
+        ]
 
-    def getDiscoItems(self, requestor, target, nodeIdentifier=''):
+    def getDiscoItems(self, requestor, target, nodeIdentifier=""):
         return []
 
     def update(self, presence):
@@ -132,35 +141,65 @@
 
         Check if we know the version of this capabilities and get the capabilities if necessary
         """
-        from_jid = jid.JID(presence['from'])
-        c_elem = presence.elements(NS_ENTITY_CAPABILITY, 'c').next()
+        from_jid = jid.JID(presence["from"])
+        c_elem = presence.elements(NS_ENTITY_CAPABILITY, "c").next()
         try:
-            c_ver = c_elem['ver']
-            c_hash = c_elem['hash']
-            c_node = c_elem['node']
+            c_ver = c_elem["ver"]
+            c_hash = c_elem["hash"]
+            c_node = c_elem["node"]
         except KeyError:
-            log.warning(_(u'Received invalid capabilities tag: %s') % c_elem.toXml())
+            log.warning(_(u"Received invalid capabilities tag: %s") % c_elem.toXml())
             return
 
         if c_ver in self.host.memory.disco.hashes:
             # we already know the hash, we update the jid entity
-            log.debug(u"hash [%(hash)s] already in cache, updating entity [%(jid)s]" % {'hash': c_ver, 'jid': from_jid.full()})
-            self.host.memory.updateEntityData(from_jid, C.ENTITY_CAP_HASH, c_ver, profile_key=self.profile)
+            log.debug(
+                u"hash [%(hash)s] already in cache, updating entity [%(jid)s]"
+                % {"hash": c_ver, "jid": from_jid.full()}
+            )
+            self.host.memory.updateEntityData(
+                from_jid, C.ENTITY_CAP_HASH, c_ver, profile_key=self.profile
+            )
             return
 
-        if c_hash != 'sha-1': # unknown hash method
-            log.warning(_(u'Unknown hash method for entity capabilities: [%(hash_method)s] (entity: %(jid)s, node: %(node)s)') % {'hash_method':c_hash, 'jid': from_jid, 'node': c_node})
+        if c_hash != "sha-1":  # unknown hash method
+            log.warning(
+                _(
+                    u"Unknown hash method for entity capabilities: [%(hash_method)s] (entity: %(jid)s, node: %(node)s)"
+                )
+                % {"hash_method": c_hash, "jid": from_jid, "node": c_node}
+            )
 
         def cb(dummy):
-            computed_hash = self.host.memory.getEntityDatum(from_jid, C.ENTITY_CAP_HASH, self.profile)
+            computed_hash = self.host.memory.getEntityDatum(
+                from_jid, C.ENTITY_CAP_HASH, self.profile
+            )
             if computed_hash != c_ver:
-                log.warning(_(u'Computed hash differ from given hash:\ngiven: [%(given_hash)s]\ncomputed: [%(computed_hash)s]\n(entity: %(jid)s, node: %(node)s)') % {'given_hash':c_ver, 'computed_hash': computed_hash, 'jid': from_jid, 'node': c_node})
+                log.warning(
+                    _(
+                        u"Computed hash differ from given hash:\ngiven: [%(given_hash)s]\ncomputed: [%(computed_hash)s]\n(entity: %(jid)s, node: %(node)s)"
+                    )
+                    % {
+                        "given_hash": c_ver,
+                        "computed_hash": computed_hash,
+                        "jid": from_jid,
+                        "node": c_node,
+                    }
+                )
 
         def eb(failure):
             if isinstance(failure.value, error.ConnectionDone):
                 return
-            msg = failure.value.condition if hasattr(failure.value, 'condition') else failure.getErrorMessage()
-            log.error(_(u"Couldn't retrieve disco info for {jid}: {error}").format(jid=from_jid.full(), error=msg))
+            msg = (
+                failure.value.condition
+                if hasattr(failure.value, "condition")
+                else failure.getErrorMessage()
+            )
+            log.error(
+                _(u"Couldn't retrieve disco info for {jid}: {error}").format(
+                    jid=from_jid.full(), error=msg
+                )
+            )
 
         d = self.host.getDiscoInfos(self.parent, from_jid)
         d.addCallbacks(cb, eb)
--- a/sat/plugins/plugin_xep_0163.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_xep_0163.py	Wed Jun 27 20:14:46 2018 +0200
@@ -21,13 +21,14 @@
 from sat.core import exceptions
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from twisted.words.xish import domish
 
 from wokkel import disco, pubsub
 from wokkel.formats import Mood
 
-NS_USER_MOOD = 'http://jabber.org/protocol/mood'
+NS_USER_MOOD = "http://jabber.org/protocol/mood"
 
 PLUGIN_INFO = {
     C.PI_NAME: "Personal Eventing Protocol Plugin",
@@ -37,19 +38,25 @@
     C.PI_DEPENDENCIES: ["XEP-0060"],
     C.PI_MAIN: "XEP_0163",
     C.PI_HANDLER: "no",
-    C.PI_DESCRIPTION: _("""Implementation of Personal Eventing Protocol""")
+    C.PI_DESCRIPTION: _("""Implementation of Personal Eventing Protocol"""),
 }
 
 
 class XEP_0163(object):
-
     def __init__(self, host):
         log.info(_("PEP plugin initialization"))
         self.host = host
         self.pep_events = set()
         self.pep_out_cb = {}
         host.trigger.add("PubSub Disco Info", self.disoInfoTrigger)
-        host.bridge.addMethod("PEPSend", ".plugin", in_sign='sa{ss}s', out_sign='', method=self.PEPSend, async=True)  # args: type(MOOD, TUNE, etc), data, profile_key;
+        host.bridge.addMethod(
+            "PEPSend",
+            ".plugin",
+            in_sign="sa{ss}s",
+            out_sign="",
+            method=self.PEPSend,
+            async=True,
+        )  # args: type(MOOD, TUNE, etc), data, profile_key;
         self.addPEPEvent("MOOD", NS_USER_MOOD, self.userMoodCB, self.sendMood)
 
     def disoInfoTrigger(self, disco_info, profile):
@@ -78,13 +85,18 @@
         self.pep_events.add(node)
         if notify:
             self.pep_events.add(node + "+notify")
+
         def filterPEPEvent(client, itemsEvent):
             """Ignore messages which are not coming from PEP (i.e. main server)
 
             @param itemsEvent(pubsub.ItemsEvent): pubsub event
             """
             if itemsEvent.sender.user or itemsEvent.sender.resource:
-                log.debug("ignoring non PEP event from {} (profile={})".format(itemsEvent.sender.full(), client.profile))
+                log.debug(
+                    "ignoring non PEP event from {} (profile={})".format(
+                        itemsEvent.sender.full(), client.profile
+                    )
+                )
                 return
             in_callback(itemsEvent, client.profile)
 
@@ -110,11 +122,14 @@
         """
         profile = self.host.memory.getProfileName(profile_key)
         if not profile:
-            log.error(_(u'Trying to send personal event with an unknown profile key [%s]') % profile_key)
+            log.error(
+                _(u"Trying to send personal event with an unknown profile key [%s]")
+                % profile_key
+            )
             raise exceptions.ProfileUnknownError
         if not event_type in self.pep_out_cb.keys():
-            log.error(_('Trying to send personal event for an unknown type'))
-            raise exceptions.DataError('Type unknown')
+            log.error(_("Trying to send personal event for an unknown type"))
+            raise exceptions.DataError("Type unknown")
         return self.pep_out_cb[event_type](data, profile)
 
     def userMoodCB(self, itemsEvent, profile):
@@ -122,7 +137,9 @@
             log.debug(_("No item found"))
             return
         try:
-            mood_elt = [child for child in itemsEvent.items[0].elements() if child.name == "mood"][0]
+            mood_elt = [
+                child for child in itemsEvent.items[0].elements() if child.name == "mood"
+            ][0]
         except IndexError:
             log.error(_("Can't find mood element in mood event"))
             return
@@ -130,8 +147,14 @@
         if not mood:
             log.debug(_("No mood found"))
             return
-        self.host.bridge.psEvent(C.PS_PEP, itemsEvent.sender.full(), itemsEvent.nodeIdentifier,
-                                 "MOOD", {"mood": mood.value or "", "text": mood.text or ""}, profile)
+        self.host.bridge.psEvent(
+            C.PS_PEP,
+            itemsEvent.sender.full(),
+            itemsEvent.nodeIdentifier,
+            "MOOD",
+            {"mood": mood.value or "", "text": mood.text or ""},
+            profile,
+        )
 
     def sendMood(self, data, profile):
         """Send XEP-0107's User Mood
@@ -139,8 +162,8 @@
         @param data: must include mood and text
         @param profile: profile which send the mood"""
         try:
-            value = data['mood'].lower()
-            text = data['text'] if 'text' in data else ''
+            value = data["mood"].lower()
+            text = data["text"] if "text" in data else ""
         except KeyError:
             raise exceptions.DataError("Mood data must contain at least 'mood' key")
         mood = UserMood(value, text)
@@ -152,7 +175,7 @@
 
     def __init__(self, value, text=None):
         Mood.__init__(self, value, text)
-        domish.Element.__init__(self, (NS_USER_MOOD, 'mood'))
+        domish.Element.__init__(self, (NS_USER_MOOD, "mood"))
         self.addElement(value)
         if text:
-            self.addElement('text', content=text)
+            self.addElement("text", content=text)
--- a/sat/plugins/plugin_xep_0166.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_xep_0166.py	Wed Jun 27 20:14:46 2018 +0200
@@ -21,6 +21,7 @@
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
 from sat.tools import xml_tools
+
 log = getLogger(__name__)
 from sat.core import exceptions
 from twisted.words.protocols.jabber import jid
@@ -37,7 +38,6 @@
 from zope.interface import implements
 
 
-
 IQ_SET = '/iq[@type="set"]'
 NS_JINGLE = "urn:xmpp:jingle:1"
 NS_JINGLE_ERROR = "urn:xmpp:jingle:errors:1"
@@ -55,24 +55,24 @@
     C.PI_PROTOCOLS: ["XEP-0166"],
     C.PI_MAIN: "XEP_0166",
     C.PI_HANDLER: "yes",
-    C.PI_DESCRIPTION: _("""Implementation of Jingle""")
+    C.PI_DESCRIPTION: _("""Implementation of Jingle"""),
 }
 
 
-ApplicationData = namedtuple('ApplicationData', ('namespace', 'handler'))
-TransportData = namedtuple('TransportData', ('namespace', 'handler', 'priority'))
+ApplicationData = namedtuple("ApplicationData", ("namespace", "handler"))
+TransportData = namedtuple("TransportData", ("namespace", "handler", "priority"))
 
 
 class XEP_0166(object):
     ROLE_INITIATOR = "initiator"
     ROLE_RESPONDER = "responder"
-    TRANSPORT_DATAGRAM='UDP'
-    TRANSPORT_STREAMING='TCP'
-    REASON_SUCCESS='success'
-    REASON_DECLINE='decline'
-    REASON_FAILED_APPLICATION='failed-application'
-    REASON_FAILED_TRANSPORT='failed-transport'
-    REASON_CONNECTIVITY_ERROR='connectivity-error'
+    TRANSPORT_DATAGRAM = "UDP"
+    TRANSPORT_STREAMING = "TCP"
+    REASON_SUCCESS = "success"
+    REASON_DECLINE = "decline"
+    REASON_FAILED_APPLICATION = "failed-application"
+    REASON_FAILED_TRANSPORT = "failed-transport"
+    REASON_CONNECTIVITY_ERROR = "connectivity-error"
     A_SESSION_INITIATE = "session-initiate"
     A_SESSION_ACCEPT = "session-accept"
     A_SESSION_TERMINATE = "session-terminate"
@@ -82,21 +82,26 @@
     A_TRANSPORT_REJECT = "transport-reject"
     A_TRANSPORT_INFO = "transport-info"
     # non standard actions
-    A_PREPARE_INITIATOR = "prepare-initiator" # initiator must prepare tranfer
-    A_PREPARE_RESPONDER = "prepare-responder" # responder must prepare tranfer
-    A_ACCEPTED_ACK = "accepted-ack" # session accepted ack has been received from initiator
-    A_START = "start" # application can start
-    A_DESTROY = "destroy" # called when a transport is destroyed (e.g. because it is remplaced). Used to do cleaning operations
+    A_PREPARE_INITIATOR = "prepare-initiator"  # initiator must prepare tranfer
+    A_PREPARE_RESPONDER = "prepare-responder"  # responder must prepare tranfer
+    A_ACCEPTED_ACK = (
+        "accepted-ack"
+    )  # session accepted ack has been received from initiator
+    A_START = "start"  # application can start
+    A_DESTROY = (
+        "destroy"
+    )  # called when a transport is destroyed (e.g. because it is remplaced). Used to do cleaning operations
 
     def __init__(self, host):
         log.info(_("plugin Jingle initialization"))
         self.host = host
-        self._applications = {} # key: namespace, value: application data
-        self._transports = {} # key: namespace, value: transport data
+        self._applications = {}  # key: namespace, value: application data
+        self._transports = {}  # key: namespace, value: transport data
         # we also keep transports by type, they are then sorted by priority
-        self._type_transports = { XEP_0166.TRANSPORT_DATAGRAM: [],
-                                  XEP_0166.TRANSPORT_STREAMING: [],
-                                }
+        self._type_transports = {
+            XEP_0166.TRANSPORT_DATAGRAM: [],
+            XEP_0166.TRANSPORT_STREAMING: [],
+        }
 
     def profileConnected(self, client):
         client.jingle_sessions = {}  # key = sid, value = session_data
@@ -115,12 +120,12 @@
     ## helpers methods to build stanzas ##
 
     def _buildJingleElt(self, client, session, action):
-        iq_elt = client.IQ('set')
-        iq_elt['from'] = client.jid.full()
-        iq_elt['to'] = session['peer_jid'].full()
+        iq_elt = client.IQ("set")
+        iq_elt["from"] = client.jid.full()
+        iq_elt["to"] = session["peer_jid"].full()
         jingle_elt = iq_elt.addElement("jingle", NS_JINGLE)
-        jingle_elt["sid"] = session['id']
-        jingle_elt['action'] = action
+        jingle_elt["sid"] = session["id"]
+        jingle_elt["action"] = action
         return iq_elt, jingle_elt
 
     def sendError(self, client, error_condition, sid, request, jingle_condition=None):
@@ -134,9 +139,13 @@
         iq_elt = error.StanzaError(error_condition).toResponse(request)
         if jingle_condition is not None:
             iq_elt.error.addElement((NS_JINGLE_ERROR, jingle_condition))
-        if error.STANZA_CONDITIONS[error_condition]['type'] == 'cancel' and sid:
+        if error.STANZA_CONDITIONS[error_condition]["type"] == "cancel" and sid:
             self._delSession(client, sid)
-            log.warning(u"Error while managing jingle session, cancelling: {condition}".format(error_condition))
+            log.warning(
+                u"Error while managing jingle session, cancelling: {condition}".format(
+                    error_condition
+                )
+            )
         client.send(iq_elt)
 
     def _terminateEb(self, failure_):
@@ -150,14 +159,16 @@
             if a list of element, add them as children of the <reason/> element
         @param session(dict): data of the session
         """
-        iq_elt, jingle_elt = self._buildJingleElt(client, session, XEP_0166.A_SESSION_TERMINATE)
-        reason_elt = jingle_elt.addElement('reason')
+        iq_elt, jingle_elt = self._buildJingleElt(
+            client, session, XEP_0166.A_SESSION_TERMINATE
+        )
+        reason_elt = jingle_elt.addElement("reason")
         if isinstance(reason, basestring):
             reason_elt.addElement(reason)
         else:
             for elt in reason:
                 reason_elt.addChild(elt)
-        self._delSession(client, session['id'])
+        self._delSession(client, session["id"])
         d = iq_elt.send()
         d.addErrback(self._terminateEb)
         return d
@@ -170,7 +181,11 @@
         @param failure_(failure.Failure): the exceptions raised
         @param sid(unicode): jingle session id
         """
-        log.warning(u"Error while sending jingle <iq/> stanza: {failure_}".format(failure_=failure_.value))
+        log.warning(
+            u"Error while sending jingle <iq/> stanza: {failure_}".format(
+                failure_=failure_.value
+            )
+        )
         self._delSession(client, sid)
 
     def _jingleErrorCb(self, fail, sid, request, client):
@@ -185,7 +200,7 @@
         """
         log.warning("Error while processing jingle request")
         if isinstance(fail, exceptions.DataError):
-            self.sendError(client, 'bad-request', sid, request)
+            self.sendError(client, "bad-request", sid, request)
         else:
             log.error("Unmanaged jingle exception")
             self._delSession(client, sid)
@@ -211,8 +226,12 @@
                     May be used to clean session
         """
         if namespace in self._applications:
-            raise exceptions.ConflictError(u"Trying to register already registered namespace {}".format(namespace))
-        self._applications[namespace] = ApplicationData(namespace=namespace, handler=handler)
+            raise exceptions.ConflictError(
+                u"Trying to register already registered namespace {}".format(namespace)
+            )
+        self._applications[namespace] = ApplicationData(
+            namespace=namespace, handler=handler
+        )
         log.debug(u"new jingle application registered")
 
     def registerTransport(self, namespace, transport_type, handler, priority=0):
@@ -227,12 +246,21 @@
                     called on several action to negociate the application or transport
         @param priority(int): priority of this transport
         """
-        assert transport_type in (XEP_0166.TRANSPORT_DATAGRAM, XEP_0166.TRANSPORT_STREAMING)
+        assert transport_type in (
+            XEP_0166.TRANSPORT_DATAGRAM,
+            XEP_0166.TRANSPORT_STREAMING,
+        )
         if namespace in self._transports:
-            raise exceptions.ConflictError(u"Trying to register already registered namespace {}".format(namespace))
-        transport_data = TransportData(namespace=namespace, handler=handler, priority=priority)
+            raise exceptions.ConflictError(
+                u"Trying to register already registered namespace {}".format(namespace)
+            )
+        transport_data = TransportData(
+            namespace=namespace, handler=handler, priority=priority
+        )
         self._type_transports[transport_type].append(transport_data)
-        self._type_transports[transport_type].sort(key=lambda transport_data: transport_data.priority, reverse=True)
+        self._type_transports[transport_type].sort(
+            key=lambda transport_data: transport_data.priority, reverse=True
+        )
         self._transports[namespace] = transport_data
         log.debug(u"new jingle transport registered")
 
@@ -247,20 +275,24 @@
         # XXX: for now we replace the transport before receiving confirmation from other peer
         #      this is acceptable because we terminate the session if transport is rejected.
         #      this behavious may change in the future.
-        content_data= session['contents'][content_name]
-        transport_data = content_data['transport_data']
+        content_data = session["contents"][content_name]
+        transport_data = content_data["transport_data"]
         try:
             transport = self._transports[transport_ns]
         except KeyError:
             raise exceptions.InternalError(u"Unkown transport")
-        yield content_data['transport'].handler.jingleHandler(client, XEP_0166.A_DESTROY, session, content_name, None)
-        content_data['transport'] = transport
+        yield content_data["transport"].handler.jingleHandler(
+            client, XEP_0166.A_DESTROY, session, content_name, None
+        )
+        content_data["transport"] = transport
         transport_data.clear()
 
-        iq_elt, jingle_elt = self._buildJingleElt(client, session, XEP_0166.A_TRANSPORT_REPLACE)
-        content_elt = jingle_elt.addElement('content')
-        content_elt['name'] = content_name
-        content_elt['creator'] = content_data['creator']
+        iq_elt, jingle_elt = self._buildJingleElt(
+            client, session, XEP_0166.A_TRANSPORT_REPLACE
+        )
+        content_elt = jingle_elt.addElement("content")
+        content_elt["name"] = content_name
+        content_elt["creator"] = content_data["creator"]
 
         transport_elt = transport.handler.jingleSessionInit(client, session, content_name)
         content_elt.addChild(transport_elt)
@@ -279,14 +311,16 @@
         # we first build iq, jingle and content element which are the same in every cases
         iq_elt, jingle_elt = self._buildJingleElt(client, session, action)
         # FIXME: XEP-0260 § 2.3 Ex 5 has an initiator attribute, but it should not according to XEP-0166 §7.1 table 1, must be checked
-        content_data= session['contents'][content_name]
-        content_elt = jingle_elt.addElement('content')
-        content_elt['name'] = content_name
-        content_elt['creator'] = content_data['creator']
+        content_data = session["contents"][content_name]
+        content_elt = jingle_elt.addElement("content")
+        content_elt["name"] = content_name
+        content_elt["creator"] = content_data["creator"]
 
         if action == XEP_0166.A_TRANSPORT_INFO:
-            context_elt = transport_elt = content_elt.addElement('transport', content_data['transport'].namespace)
-            transport_elt['sid'] = content_data['transport_data']['sid']
+            context_elt = transport_elt = content_elt.addElement(
+                "transport", content_data["transport"].namespace
+            )
+            transport_elt["sid"] = content_data["transport_data"]["sid"]
         else:
             raise exceptions.InternalError(u"unmanaged action {}".format(action))
 
@@ -298,7 +332,7 @@
         @param session(dict): jingle session data
         @return (tuple[domish.Element, domish.Element]): parent <iq> element, <jingle> element
         """
-        return  self._buildJingleElt(client, session, XEP_0166.A_SESSION_INFO)
+        return self._buildJingleElt(client, session, XEP_0166.A_SESSION_INFO)
 
     @defer.inlineCallbacks
     def initiate(self, client, peer_jid, contents):
@@ -318,74 +352,88 @@
                 - app_kwargs(dict): keyword args to pass to the application plugin
         @return D(unicode): jingle session id
         """
-        assert contents # there must be at least one content
+        assert contents  # there must be at least one content
         if peer_jid == client.jid:
             raise ValueError(_(u"You can't do a jingle session with yourself"))
         initiator = client.jid
         sid = unicode(uuid.uuid4())
         # TODO: session cleaning after timeout ?
-        session = client.jingle_sessions[sid] = {'id': sid,
-                                                 'state': STATE_PENDING,
-                                                 'initiator': initiator,
-                                                 'role': XEP_0166.ROLE_INITIATOR,
-                                                 'peer_jid': peer_jid,
-                                                 'started': time.time(),
-                                                 'contents': {}
-                                                }
-        iq_elt, jingle_elt = self._buildJingleElt(client, session, XEP_0166.A_SESSION_INITIATE)
+        session = client.jingle_sessions[sid] = {
+            "id": sid,
+            "state": STATE_PENDING,
+            "initiator": initiator,
+            "role": XEP_0166.ROLE_INITIATOR,
+            "peer_jid": peer_jid,
+            "started": time.time(),
+            "contents": {},
+        }
+        iq_elt, jingle_elt = self._buildJingleElt(
+            client, session, XEP_0166.A_SESSION_INITIATE
+        )
         jingle_elt["initiator"] = initiator.full()
 
-        contents_dict = session['contents']
+        contents_dict = session["contents"]
 
         for content in contents:
             # we get the application plugin
-            app_ns = content['app_ns']
+            app_ns = content["app_ns"]
             try:
                 application = self._applications[app_ns]
             except KeyError:
-                raise exceptions.InternalError(u"No application registered for {}".format(app_ns))
+                raise exceptions.InternalError(
+                    u"No application registered for {}".format(app_ns)
+                )
 
             # and the transport plugin
-            transport_type = content.get('transport_type', XEP_0166.TRANSPORT_STREAMING)
+            transport_type = content.get("transport_type", XEP_0166.TRANSPORT_STREAMING)
             try:
                 transport = self._type_transports[transport_type][0]
             except IndexError:
-                raise exceptions.InternalError(u"No transport registered for {}".format(transport_type))
+                raise exceptions.InternalError(
+                    u"No transport registered for {}".format(transport_type)
+                )
 
             # we build the session data
-            content_data = {'application': application,
-                            'application_data': {},
-                            'transport': transport,
-                            'transport_data': {},
-                            'creator': XEP_0166.ROLE_INITIATOR,
-                            'senders': content.get('senders', 'both'),
-                            }
+            content_data = {
+                "application": application,
+                "application_data": {},
+                "transport": transport,
+                "transport_data": {},
+                "creator": XEP_0166.ROLE_INITIATOR,
+                "senders": content.get("senders", "both"),
+            }
             try:
-                content_name = content['name']
+                content_name = content["name"]
             except KeyError:
                 content_name = unicode(uuid.uuid4())
             else:
                 if content_name in contents_dict:
-                    raise exceptions.InternalError('There is already a content with this name')
+                    raise exceptions.InternalError(
+                        "There is already a content with this name"
+                    )
             contents_dict[content_name] = content_data
 
             # we construct the content element
-            content_elt = jingle_elt.addElement('content')
-            content_elt['creator'] = content_data['creator']
-            content_elt['name'] = content_name
+            content_elt = jingle_elt.addElement("content")
+            content_elt["creator"] = content_data["creator"]
+            content_elt["name"] = content_name
             try:
-                content_elt['senders'] = content['senders']
+                content_elt["senders"] = content["senders"]
             except KeyError:
                 pass
 
             # then the description element
-            app_args = content.get('app_args', [])
-            app_kwargs = content.get('app_kwargs', {})
-            desc_elt = yield application.handler.jingleSessionInit(client, session, content_name, *app_args, **app_kwargs)
+            app_args = content.get("app_args", [])
+            app_kwargs = content.get("app_kwargs", {})
+            desc_elt = yield application.handler.jingleSessionInit(
+                client, session, content_name, *app_args, **app_kwargs
+            )
             content_elt.addChild(desc_elt)
 
             # and the transport one
-            transport_elt = yield transport.handler.jingleSessionInit(client, session, content_name)
+            transport_elt = yield transport.handler.jingleSessionInit(
+                client, session, content_name
+            )
             content_elt.addChild(transport_elt)
 
         try:
@@ -410,17 +458,24 @@
         @param content_name(unicode): name of the content terminated
         @param reason(unicode): reason of the termination
         """
-        contents = session['contents']
+        contents = session["contents"]
         del contents[content_name]
         if not contents:
             self.terminate(client, reason, session)
 
     ## defaults methods called when plugin doesn't have them ##
 
-    def jingleRequestConfirmationDefault(self, client, action, session, content_name, desc_elt):
+    def jingleRequestConfirmationDefault(
+        self, client, action, session, content_name, desc_elt
+    ):
         """This method request confirmation for a jingle session"""
         log.debug(u"Using generic jingle confirmation method")
-        return  xml_tools.deferConfirm(self.host, _(CONFIRM_TXT).format(entity=session['peer_jid'].full()), _('Confirm Jingle session'), profile=client.profile)
+        return xml_tools.deferConfirm(
+            self.host,
+            _(CONFIRM_TXT).format(entity=session["peer_jid"].full()),
+            _("Confirm Jingle session"),
+            profile=client.profile,
+        )
 
     ## jingle events ##
 
@@ -432,29 +487,29 @@
         @param request(domish.Element): received IQ request
         """
         request.handled = True
-        jingle_elt = request.elements(NS_JINGLE, 'jingle').next()
+        jingle_elt = request.elements(NS_JINGLE, "jingle").next()
 
         # first we need the session id
         try:
-            sid = jingle_elt['sid']
+            sid = jingle_elt["sid"]
             if not sid:
                 raise KeyError
         except KeyError:
             log.warning(u"Received jingle request has no sid attribute")
-            self.sendError(client, 'bad-request', None, request)
+            self.sendError(client, "bad-request", None, request)
             return
 
         # then the action
         try:
-            action = jingle_elt['action']
+            action = jingle_elt["action"]
             if not action:
                 raise KeyError
         except KeyError:
             log.warning(u"Received jingle request has no action")
-            self.sendError(client, 'bad-request', None, request)
+            self.sendError(client, "bad-request", None, request)
             return
 
-        peer_jid = jid.JID(request['from'])
+        peer_jid = jid.JID(request["from"])
 
         # we get or create the session
         try:
@@ -463,32 +518,41 @@
             if action == XEP_0166.A_SESSION_INITIATE:
                 pass
             elif action == XEP_0166.A_SESSION_TERMINATE:
-                log.debug(u"ignoring session terminate action (inexisting session id): {request_id} [{profile}]".format(
-                    request_id=sid,
-                    profile = client.profile))
+                log.debug(
+                    u"ignoring session terminate action (inexisting session id): {request_id} [{profile}]".format(
+                        request_id=sid, profile=client.profile
+                    )
+                )
                 return
             else:
-                log.warning(u"Received request for an unknown session id: {request_id} [{profile}]".format(
-                    request_id=sid,
-                    profile = client.profile))
-                self.sendError(client, 'item-not-found', None, request, 'unknown-session')
+                log.warning(
+                    u"Received request for an unknown session id: {request_id} [{profile}]".format(
+                        request_id=sid, profile=client.profile
+                    )
+                )
+                self.sendError(client, "item-not-found", None, request, "unknown-session")
                 return
 
-            session = client.jingle_sessions[sid] = {'id': sid,
-                                                     'state': STATE_PENDING,
-                                                     'initiator': peer_jid,
-                                                     'role': XEP_0166.ROLE_RESPONDER,
-                                                     'peer_jid': peer_jid,
-                                                     'started': time.time(),
-                                                    }
+            session = client.jingle_sessions[sid] = {
+                "id": sid,
+                "state": STATE_PENDING,
+                "initiator": peer_jid,
+                "role": XEP_0166.ROLE_RESPONDER,
+                "peer_jid": peer_jid,
+                "started": time.time(),
+            }
         else:
-            if session['peer_jid'] != peer_jid:
-                log.warning(u"sid conflict ({}), the jid doesn't match. Can be a collision, a hack attempt, or a bad sid generation".format(sid))
-                self.sendError(client, 'service-unavailable', sid, request)
+            if session["peer_jid"] != peer_jid:
+                log.warning(
+                    u"sid conflict ({}), the jid doesn't match. Can be a collision, a hack attempt, or a bad sid generation".format(
+                        sid
+                    )
+                )
+                self.sendError(client, "service-unavailable", sid, request)
                 return
-            if session['id'] != sid:
+            if session["id"] != sid:
                 log.error(u"session id doesn't match")
-                self.sendError(client, 'service-unavailable', sid, request)
+                self.sendError(client, "service-unavailable", sid, request)
                 raise exceptions.InternalError
 
         if action == XEP_0166.A_SESSION_INITIATE:
@@ -512,7 +576,17 @@
 
     ## Actions callbacks ##
 
-    def _parseElements(self, jingle_elt, session, request, client, new=False, creator=ROLE_INITIATOR, with_application=True, with_transport=True):
+    def _parseElements(
+        self,
+        jingle_elt,
+        session,
+        request,
+        client,
+        new=False,
+        creator=ROLE_INITIATOR,
+        with_application=True,
+        with_transport=True,
+    ):
         """Parse contents elements and fill contents_dict accordingly
 
         after the parsing, contents_dict will containt handlers, "desc_elt" and "transport_elt"
@@ -527,85 +601,98 @@
         @param with_transport(bool): if True, raise an error if there is no <transport> element else ignore it
         @raise exceptions.CancelError: the error is treated and the calling method can cancel the treatment (i.e. return)
         """
-        contents_dict = session['contents']
-        content_elts = jingle_elt.elements(NS_JINGLE, 'content')
+        contents_dict = session["contents"]
+        content_elts = jingle_elt.elements(NS_JINGLE, "content")
 
         for content_elt in content_elts:
-            name = content_elt['name']
+            name = content_elt["name"]
 
             if new:
                 # the content must not exist, we check it
                 if not name or name in contents_dict:
-                    self.sendError(client, 'bad-request', session['id'], request)
+                    self.sendError(client, "bad-request", session["id"], request)
                     raise exceptions.CancelError
-                content_data = contents_dict[name] = {'creator': creator,
-                                                      'senders': content_elt.attributes.get('senders', 'both'),
-                                                     }
+                content_data = contents_dict[name] = {
+                    "creator": creator,
+                    "senders": content_elt.attributes.get("senders", "both"),
+                }
             else:
                 # the content must exist, we check it
                 try:
                     content_data = contents_dict[name]
                 except KeyError:
                     log.warning(u"Other peer try to access an unknown content")
-                    self.sendError(client, 'bad-request', session['id'], request)
+                    self.sendError(client, "bad-request", session["id"], request)
                     raise exceptions.CancelError
 
             # application
             if with_application:
                 desc_elt = content_elt.description
                 if not desc_elt:
-                    self.sendError(client, 'bad-request', session['id'], request)
+                    self.sendError(client, "bad-request", session["id"], request)
                     raise exceptions.CancelError
 
                 if new:
                     # the content is new, we need to check and link the application
                     app_ns = desc_elt.uri
                     if not app_ns or app_ns == NS_JINGLE:
-                        self.sendError(client, 'bad-request', session['id'], request)
+                        self.sendError(client, "bad-request", session["id"], request)
                         raise exceptions.CancelError
 
                     try:
                         application = self._applications[app_ns]
                     except KeyError:
-                        log.warning(u"Unmanaged application namespace [{}]".format(app_ns))
-                        self.sendError(client, 'service-unavailable', session['id'], request)
+                        log.warning(
+                            u"Unmanaged application namespace [{}]".format(app_ns)
+                        )
+                        self.sendError(
+                            client, "service-unavailable", session["id"], request
+                        )
                         raise exceptions.CancelError
 
-                    content_data['application'] = application
-                    content_data['application_data'] = {}
+                    content_data["application"] = application
+                    content_data["application_data"] = {}
                 else:
                     # the content exists, we check that we have not a former desc_elt
-                    if 'desc_elt' in content_data:
-                        raise exceptions.InternalError(u"desc_elt should not exist at this point")
+                    if "desc_elt" in content_data:
+                        raise exceptions.InternalError(
+                            u"desc_elt should not exist at this point"
+                        )
 
-                content_data['desc_elt'] = desc_elt
+                content_data["desc_elt"] = desc_elt
 
             # transport
             if with_transport:
                 transport_elt = content_elt.transport
                 if not transport_elt:
-                    self.sendError(client, 'bad-request', session['id'], request)
+                    self.sendError(client, "bad-request", session["id"], request)
                     raise exceptions.CancelError
 
                 if new:
                     # the content is new, we need to check and link the transport
                     transport_ns = transport_elt.uri
                     if not app_ns or app_ns == NS_JINGLE:
-                        self.sendError(client, 'bad-request', session['id'], request)
+                        self.sendError(client, "bad-request", session["id"], request)
                         raise exceptions.CancelError
 
                     try:
                         transport = self._transports[transport_ns]
                     except KeyError:
-                        raise exceptions.InternalError(u"No transport registered for namespace {}".format(transport_ns))
-                    content_data['transport'] = transport
-                    content_data['transport_data'] = {}
+                        raise exceptions.InternalError(
+                            u"No transport registered for namespace {}".format(
+                                transport_ns
+                            )
+                        )
+                    content_data["transport"] = transport
+                    content_data["transport_data"] = {}
                 else:
                     # the content exists, we check that we have not a former transport_elt
-                    if 'transport_elt' in content_data:
-                        raise exceptions.InternalError(u"transport_elt should not exist at this point")
+                    if "transport_elt" in content_data:
+                        raise exceptions.InternalError(
+                            u"transport_elt should not exist at this point"
+                        )
 
-                content_data['transport_elt'] = transport_elt
+                content_data["transport_elt"] = transport_elt
 
     def _ignore(self, client, action, session, content_name, elt):
         """Dummy method used when not exception must be raised if a method is not implemented in _callPlugins
@@ -614,10 +701,19 @@
         """
         return elt
 
-    def _callPlugins(self, client, action, session, app_method_name='jingleHandler',
-        transp_method_name='jingleHandler',
-        app_default_cb=None, transp_default_cb=None, delete=True,
-        elements=True, force_element=None):
+    def _callPlugins(
+        self,
+        client,
+        action,
+        session,
+        app_method_name="jingleHandler",
+        transp_method_name="jingleHandler",
+        app_default_cb=None,
+        transp_default_cb=None,
+        delete=True,
+        elements=True,
+        force_element=None,
+    ):
         """Call application and transport plugin methods for all contents
 
         @param action(unicode): jingle action name
@@ -640,12 +736,13 @@
         @return (list[defer.Deferred]): list of launched Deferred
         @raise exceptions.NotFound: method is not implemented
         """
-        contents_dict = session['contents']
+        contents_dict = session["contents"]
         defers_list = []
         for content_name, content_data in contents_dict.iteritems():
             for method_name, handler_key, default_cb, elt_name in (
-                (app_method_name, 'application', app_default_cb, 'desc_elt'),
-                (transp_method_name, 'transport', transp_default_cb, 'transport_elt')):
+                (app_method_name, "application", app_default_cb, "desc_elt"),
+                (transp_method_name, "transport", transp_default_cb, "transport_elt"),
+            ):
                 if method_name is None:
                     continue
 
@@ -654,14 +751,18 @@
                     method = getattr(handler, method_name)
                 except AttributeError:
                     if default_cb is None:
-                        raise exceptions.NotFound(u'{} not implemented !'.format(method_name))
+                        raise exceptions.NotFound(
+                            u"{} not implemented !".format(method_name)
+                        )
                     else:
                         method = default_cb
                 if elements:
                     elt = content_data.pop(elt_name) if delete else content_data[elt_name]
                 else:
                     elt = force_element
-                d = defer.maybeDeferred(method, client, action, session, content_name, elt)
+                d = defer.maybeDeferred(
+                    method, client, action, session, content_name, elt
+                )
                 defers_list.append(d)
 
         return defers_list
@@ -678,30 +779,42 @@
         @param jingle_elt(domish.Element): <jingle> element
         @param session(dict): session data
         """
-        if 'contents' in session:
-            raise exceptions.InternalError("Contents dict should not already exist at this point")
-        session['contents'] = contents_dict = {}
+        if "contents" in session:
+            raise exceptions.InternalError(
+                "Contents dict should not already exist at this point"
+            )
+        session["contents"] = contents_dict = {}
 
         try:
-            self._parseElements(jingle_elt, session, request, client, True, XEP_0166.ROLE_INITIATOR)
+            self._parseElements(
+                jingle_elt, session, request, client, True, XEP_0166.ROLE_INITIATOR
+            )
         except exceptions.CancelError:
             return
 
         if not contents_dict:
             # there MUST be at least one content
-            self.sendError(client, 'bad-request', session['id'], request)
+            self.sendError(client, "bad-request", session["id"], request)
             return
 
         # at this point we can send the <iq/> result to confirm reception of the request
-        client.send(xmlstream.toResponse(request, 'result'))
+        client.send(xmlstream.toResponse(request, "result"))
 
         # we now request each application plugin confirmation
         # and if all are accepted, we can accept the session
-        confirm_defers = self._callPlugins(client, XEP_0166.A_SESSION_INITIATE, session, 'jingleRequestConfirmation', None, self.jingleRequestConfirmationDefault, delete=False)
+        confirm_defers = self._callPlugins(
+            client,
+            XEP_0166.A_SESSION_INITIATE,
+            session,
+            "jingleRequestConfirmation",
+            None,
+            self.jingleRequestConfirmationDefault,
+            delete=False,
+        )
 
         confirm_dlist = defer.gatherResults(confirm_defers)
         confirm_dlist.addCallback(self._confirmationCb, session, jingle_elt, client)
-        confirm_dlist.addErrback(self._jingleErrorCb, session['id'], request, client)
+        confirm_dlist.addErrback(self._jingleErrorCb, session["id"], request, client)
 
     def _confirmationCb(self, confirm_results, session, jingle_elt, client):
         """Method called when confirmation from user has been received
@@ -716,8 +829,10 @@
         if not confirmed:
             return self.terminate(client, XEP_0166.REASON_DECLINE, session)
 
-        iq_elt, jingle_elt = self._buildJingleElt(client, session, XEP_0166.A_SESSION_ACCEPT)
-        jingle_elt['responder'] = client.jid.full()
+        iq_elt, jingle_elt = self._buildJingleElt(
+            client, session, XEP_0166.A_SESSION_ACCEPT
+        )
+        jingle_elt["responder"] = client.jid.full()
 
         # contents
 
@@ -726,52 +841,87 @@
 
         defers_list = []
 
-        for content_name, content_data in session['contents'].iteritems():
-            content_elt = jingle_elt.addElement('content')
-            content_elt['creator'] = XEP_0166.ROLE_INITIATOR
-            content_elt['name'] = content_name
+        for content_name, content_data in session["contents"].iteritems():
+            content_elt = jingle_elt.addElement("content")
+            content_elt["creator"] = XEP_0166.ROLE_INITIATOR
+            content_elt["name"] = content_name
 
-            application = content_data['application']
+            application = content_data["application"]
             app_session_accept_cb = application.handler.jingleHandler
 
-            app_d = defer.maybeDeferred(app_session_accept_cb, client,
-                XEP_0166.A_SESSION_INITIATE, session, content_name, content_data.pop('desc_elt'))
+            app_d = defer.maybeDeferred(
+                app_session_accept_cb,
+                client,
+                XEP_0166.A_SESSION_INITIATE,
+                session,
+                content_name,
+                content_data.pop("desc_elt"),
+            )
             app_d.addCallback(addElement, content_elt)
             defers_list.append(app_d)
 
-            transport = content_data['transport']
+            transport = content_data["transport"]
             transport_session_accept_cb = transport.handler.jingleHandler
 
-            transport_d = defer.maybeDeferred(transport_session_accept_cb, client,
-                XEP_0166.A_SESSION_INITIATE, session, content_name, content_data.pop('transport_elt'))
+            transport_d = defer.maybeDeferred(
+                transport_session_accept_cb,
+                client,
+                XEP_0166.A_SESSION_INITIATE,
+                session,
+                content_name,
+                content_data.pop("transport_elt"),
+            )
             transport_d.addCallback(addElement, content_elt)
             defers_list.append(transport_d)
 
         d_list = defer.DeferredList(defers_list)
-        d_list.addCallback(lambda dummy: self._callPlugins(client, XEP_0166.A_PREPARE_RESPONDER, session, app_method_name=None, elements=False))
+        d_list.addCallback(
+            lambda dummy: self._callPlugins(
+                client,
+                XEP_0166.A_PREPARE_RESPONDER,
+                session,
+                app_method_name=None,
+                elements=False,
+            )
+        )
         d_list.addCallback(lambda dummy: iq_elt.send())
+
         def changeState(dummy, session):
-            session['state'] = STATE_ACTIVE
+            session["state"] = STATE_ACTIVE
 
         d_list.addCallback(changeState, session)
-        d_list.addCallback(lambda dummy: self._callPlugins(client, XEP_0166.A_ACCEPTED_ACK, session, elements=False))
-        d_list.addErrback(self._iqError, session['id'], client)
+        d_list.addCallback(
+            lambda dummy: self._callPlugins(
+                client, XEP_0166.A_ACCEPTED_ACK, session, elements=False
+            )
+        )
+        d_list.addErrback(self._iqError, session["id"], client)
         return d_list
 
     def onSessionTerminate(self, client, request, jingle_elt, session):
         # TODO: check reason, display a message to user if needed
-        log.debug("Jingle Session {} terminated".format(session['id']))
+        log.debug("Jingle Session {} terminated".format(session["id"]))
         try:
-            reason_elt = jingle_elt.elements(NS_JINGLE, 'reason').next()
+            reason_elt = jingle_elt.elements(NS_JINGLE, "reason").next()
         except StopIteration:
             log.warning(u"No reason given for session termination")
-            reason_elt = jingle_elt.addElement('reason')
+            reason_elt = jingle_elt.addElement("reason")
 
-        terminate_defers = self._callPlugins(client, XEP_0166.A_SESSION_TERMINATE, session, 'jingleTerminate', 'jingleTerminate', self._ignore, self._ignore, elements=False, force_element=reason_elt)
+        terminate_defers = self._callPlugins(
+            client,
+            XEP_0166.A_SESSION_TERMINATE,
+            session,
+            "jingleTerminate",
+            "jingleTerminate",
+            self._ignore,
+            self._ignore,
+            elements=False,
+            force_element=reason_elt,
+        )
         terminate_dlist = defer.DeferredList(terminate_defers)
 
-        terminate_dlist.addCallback(lambda dummy: self._delSession(client, session['id']))
-        client.send(xmlstream.toResponse(request, 'result'))
+        terminate_dlist.addCallback(lambda dummy: self._delSession(client, session["id"]))
+        client.send(xmlstream.toResponse(request, "result"))
 
     def onSessionAccept(self, client, request, jingle_elt, session):
         """Method called once session is accepted
@@ -782,7 +932,7 @@
         @param jingle_elt(domish.Element): the <jingle> element
         @param session(dict): session data
         """
-        log.debug(u"Jingle session {} has been accepted".format(session['id']))
+        log.debug(u"Jingle session {} has been accepted".format(session["id"]))
 
         try:
             self._parseElements(jingle_elt, session, request, client)
@@ -790,9 +940,9 @@
             return
 
         # at this point we can send the <iq/> result to confirm reception of the request
-        client.send(xmlstream.toResponse(request, 'result'))
+        client.send(xmlstream.toResponse(request, "result"))
         # and change the state
-        session['state'] = STATE_ACTIVE
+        session["state"] = STATE_ACTIVE
 
         negociate_defers = []
         negociate_defers = self._callPlugins(client, XEP_0166.A_SESSION_ACCEPT, session)
@@ -800,15 +950,21 @@
         negociate_dlist = defer.DeferredList(negociate_defers)
 
         # after negociations we start the transfer
-        negociate_dlist.addCallback(lambda dummy: self._callPlugins(client, XEP_0166.A_START, session, app_method_name=None, elements=False))
+        negociate_dlist.addCallback(
+            lambda dummy: self._callPlugins(
+                client, XEP_0166.A_START, session, app_method_name=None, elements=False
+            )
+        )
 
     def _onSessionCb(self, result, client, request, jingle_elt, session):
-        client.send(xmlstream.toResponse(request, 'result'))
+        client.send(xmlstream.toResponse(request, "result"))
 
     def _onSessionEb(self, failure_, client, request, jingle_elt, session):
         log.error(u"Error while handling onSessionInfo: {}".format(failure_.value))
         # XXX: only error managed so far, maybe some applications/transports need more
-        self.sendError(client, 'feature-not-implemented', None, request, 'unsupported-info')
+        self.sendError(
+            client, "feature-not-implemented", None, request, "unsupported-info"
+        )
 
     def onSessionInfo(self, client, request, jingle_elt, session):
         """Method called when a session-info action is received from other peer
@@ -821,14 +977,21 @@
         """
         if not jingle_elt.children:
             # this is a session ping, see XEP-0166 §6.8
-            client.send(xmlstream.toResponse(request, 'result'))
+            client.send(xmlstream.toResponse(request, "result"))
             return
 
         try:
             # XXX: session-info is most likely only used for application, so we don't call transport plugins
             #      if a future transport use it, this behaviour must be adapted
-            defers = self._callPlugins(client, XEP_0166.A_SESSION_INFO, session, 'jingleSessionInfo', None,
-                elements=False, force_element=jingle_elt)
+            defers = self._callPlugins(
+                client,
+                XEP_0166.A_SESSION_INFO,
+                session,
+                "jingleSessionInfo",
+                None,
+                elements=False,
+                force_element=jingle_elt,
+            )
         except exceptions.NotFound as e:
             self._onSessionEb(failure.Failure(e), client, request, jingle_elt, session)
             return
@@ -849,32 +1012,40 @@
         """
         log.debug(u"Other peer wants to replace the transport")
         try:
-            self._parseElements(jingle_elt, session, request, client, with_application=False)
+            self._parseElements(
+                jingle_elt, session, request, client, with_application=False
+            )
         except exceptions.CancelError:
             defer.returnValue(None)
 
-        client.send(xmlstream.toResponse(request, 'result'))
+        client.send(xmlstream.toResponse(request, "result"))
 
         content_name = None
         to_replace = []
 
-        for content_name, content_data in session['contents'].iteritems():
+        for content_name, content_data in session["contents"].iteritems():
             try:
-                transport_elt = content_data.pop('transport_elt')
+                transport_elt = content_data.pop("transport_elt")
             except KeyError:
                 continue
             transport_ns = transport_elt.uri
             try:
                 transport = self._transports[transport_ns]
             except KeyError:
-                log.warning(u"Other peer want to replace current transport with an unknown one: {}".format(transport_ns))
+                log.warning(
+                    u"Other peer want to replace current transport with an unknown one: {}".format(
+                        transport_ns
+                    )
+                )
                 content_name = None
                 break
             to_replace.append((content_name, content_data, transport, transport_elt))
 
         if content_name is None:
             # wa can't accept the replacement
-            iq_elt, reject_jingle_elt = self._buildJingleElt(client, session, XEP_0166.A_TRANSPORT_REJECT)
+            iq_elt, reject_jingle_elt = self._buildJingleElt(
+                client, session, XEP_0166.A_TRANSPORT_REJECT
+            )
             for child in jingle_elt.children:
                 reject_jingle_elt.addChild(child)
 
@@ -883,21 +1054,29 @@
 
         # at this point, everything is alright and we can replace the transport(s)
         # this is similar to an session-accept action, but for transports only
-        iq_elt, accept_jingle_elt = self._buildJingleElt(client, session, XEP_0166.A_TRANSPORT_ACCEPT)
+        iq_elt, accept_jingle_elt = self._buildJingleElt(
+            client, session, XEP_0166.A_TRANSPORT_ACCEPT
+        )
         for content_name, content_data, transport, transport_elt in to_replace:
             # we can now actually replace the transport
-            yield content_data['transport'].handler.jingleHandler(client, XEP_0166.A_DESTROY, session, content_name, None)
-            content_data['transport'] = transport
-            content_data['transport_data'].clear()
+            yield content_data["transport"].handler.jingleHandler(
+                client, XEP_0166.A_DESTROY, session, content_name, None
+            )
+            content_data["transport"] = transport
+            content_data["transport_data"].clear()
             # and build the element
-            content_elt = accept_jingle_elt.addElement('content')
-            content_elt['name'] = content_name
-            content_elt['creator'] = content_data['creator']
+            content_elt = accept_jingle_elt.addElement("content")
+            content_elt["name"] = content_name
+            content_elt["creator"] = content_data["creator"]
             # we notify the transport and insert its <transport/> in the answer
-            accept_transport_elt = yield transport.handler.jingleHandler(client, XEP_0166.A_TRANSPORT_REPLACE, session, content_name, transport_elt)
+            accept_transport_elt = yield transport.handler.jingleHandler(
+                client, XEP_0166.A_TRANSPORT_REPLACE, session, content_name, transport_elt
+            )
             content_elt.addChild(accept_transport_elt)
             # there is no confirmation needed here, so we can directly prepare it
-            yield transport.handler.jingleHandler(client, XEP_0166.A_PREPARE_RESPONDER, session, content_name, None)
+            yield transport.handler.jingleHandler(
+                client, XEP_0166.A_PREPARE_RESPONDER, session, content_name, None
+            )
 
         iq_elt.send()
 
@@ -912,20 +1091,28 @@
         log.debug(u"new transport has been accepted")
 
         try:
-            self._parseElements(jingle_elt, session, request, client, with_application=False)
+            self._parseElements(
+                jingle_elt, session, request, client, with_application=False
+            )
         except exceptions.CancelError:
             return
 
         # at this point we can send the <iq/> result to confirm reception of the request
-        client.send(xmlstream.toResponse(request, 'result'))
+        client.send(xmlstream.toResponse(request, "result"))
 
         negociate_defers = []
-        negociate_defers = self._callPlugins(client, XEP_0166.A_TRANSPORT_ACCEPT, session, app_method_name=None)
+        negociate_defers = self._callPlugins(
+            client, XEP_0166.A_TRANSPORT_ACCEPT, session, app_method_name=None
+        )
 
         negociate_dlist = defer.DeferredList(negociate_defers)
 
         # after negociations we start the transfer
-        negociate_dlist.addCallback(lambda dummy: self._callPlugins(client, XEP_0166.A_START, session, app_method_name=None, elements=False))
+        negociate_dlist.addCallback(
+            lambda dummy: self._callPlugins(
+                client, XEP_0166.A_START, session, app_method_name=None, elements=False
+            )
+        )
 
     def onTransportReject(self, client, request, jingle_elt, session):
         """Method called when a transport replacement is refused
@@ -937,7 +1124,7 @@
         """
         # XXX: for now, we terminate the session in case of transport-reject
         #      this behaviour may change in the future
-        self.terminate(client, 'failed-transport', session)
+        self.terminate(client, "failed-transport", session)
 
     def onTransportInfo(self, client, request, jingle_elt, session):
         """Method called when a transport-info action is received from other peer
@@ -948,23 +1135,31 @@
         @param jingle_elt(domish.Element): the <jingle> element
         @param session(dict): session data
         """
-        log.debug(u"Jingle session {} has been accepted".format(session['id']))
+        log.debug(u"Jingle session {} has been accepted".format(session["id"]))
 
         try:
-            self._parseElements(jingle_elt, session, request, client, with_application=False)
+            self._parseElements(
+                jingle_elt, session, request, client, with_application=False
+            )
         except exceptions.CancelError:
             return
 
         # The parsing was OK, we send the <iq> result
-        client.send(xmlstream.toResponse(request, 'result'))
+        client.send(xmlstream.toResponse(request, "result"))
 
-        for content_name, content_data in session['contents'].iteritems():
+        for content_name, content_data in session["contents"].iteritems():
             try:
-                transport_elt = content_data.pop('transport_elt')
+                transport_elt = content_data.pop("transport_elt")
             except KeyError:
                 continue
             else:
-                content_data['transport'].handler.jingleHandler(client, XEP_0166.A_TRANSPORT_INFO, session, content_name, transport_elt)
+                content_data["transport"].handler.jingleHandler(
+                    client,
+                    XEP_0166.A_TRANSPORT_INFO,
+                    session,
+                    content_name,
+                    transport_elt,
+                )
 
 
 class XEP_0166_handler(xmlstream.XMPPHandler):
@@ -974,10 +1169,12 @@
         self.plugin_parent = plugin_parent
 
     def connectionInitialized(self):
-        self.xmlstream.addObserver(JINGLE_REQUEST, self.plugin_parent._onJingleRequest, client=self.parent)
+        self.xmlstream.addObserver(
+            JINGLE_REQUEST, self.plugin_parent._onJingleRequest, client=self.parent
+        )
 
-    def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
+    def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
         return [disco.DiscoFeature(NS_JINGLE)]
 
-    def getDiscoItems(self, requestor, target, nodeIdentifier=''):
+    def getDiscoItems(self, requestor, target, nodeIdentifier=""):
         return []
--- a/sat/plugins/plugin_xep_0184.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_xep_0184.py	Wed Jun 27 20:14:46 2018 +0200
@@ -22,27 +22,37 @@
 from twisted.internet import reactor
 from twisted.words.protocols.jabber import xmlstream, jid
 from twisted.words.xish import domish
+
 log = getLogger(__name__)
 
 from wokkel import disco, iwokkel
 from zope.interface import implements
+
 try:
     from twisted.words.protocols.xmlstream import XMPPHandler
 except ImportError:
     from wokkel.subprotocols import XMPPHandler
 
 
-NS_MESSAGE_DELIVERY_RECEIPTS = 'urn:xmpp:receipts'
+NS_MESSAGE_DELIVERY_RECEIPTS = "urn:xmpp:receipts"
 
-MSG = 'message'
+MSG = "message"
 
-MSG_CHAT = '/'+MSG+'[@type="chat"]'
-MSG_CHAT_MESSAGE_DELIVERY_RECEIPTS_REQUEST  = MSG_CHAT+'/request[@xmlns="'+NS_MESSAGE_DELIVERY_RECEIPTS+'"]'
-MSG_CHAT_MESSAGE_DELIVERY_RECEIPTS_RECEIVED = MSG_CHAT+'/received[@xmlns="'+NS_MESSAGE_DELIVERY_RECEIPTS+'"]'
+MSG_CHAT = "/" + MSG + '[@type="chat"]'
+MSG_CHAT_MESSAGE_DELIVERY_RECEIPTS_REQUEST = (
+    MSG_CHAT + '/request[@xmlns="' + NS_MESSAGE_DELIVERY_RECEIPTS + '"]'
+)
+MSG_CHAT_MESSAGE_DELIVERY_RECEIPTS_RECEIVED = (
+    MSG_CHAT + '/received[@xmlns="' + NS_MESSAGE_DELIVERY_RECEIPTS + '"]'
+)
 
-MSG_NORMAL = '/'+MSG+'[@type="normal"]'
-MSG_NORMAL_MESSAGE_DELIVERY_RECEIPTS_REQUEST  = MSG_NORMAL+'/request[@xmlns="'+NS_MESSAGE_DELIVERY_RECEIPTS+'"]'
-MSG_NORMAL_MESSAGE_DELIVERY_RECEIPTS_RECEIVED = MSG_NORMAL+'/received[@xmlns="'+NS_MESSAGE_DELIVERY_RECEIPTS+'"]'
+MSG_NORMAL = "/" + MSG + '[@type="normal"]'
+MSG_NORMAL_MESSAGE_DELIVERY_RECEIPTS_REQUEST = (
+    MSG_NORMAL + '/request[@xmlns="' + NS_MESSAGE_DELIVERY_RECEIPTS + '"]'
+)
+MSG_NORMAL_MESSAGE_DELIVERY_RECEIPTS_RECEIVED = (
+    MSG_NORMAL + '/received[@xmlns="' + NS_MESSAGE_DELIVERY_RECEIPTS + '"]'
+)
 
 
 PARAM_KEY = "Privacy"
@@ -51,25 +61,26 @@
 
 
 PLUGIN_INFO = {
-C.PI_NAME: "XEP-0184 Plugin",
-C.PI_IMPORT_NAME: "XEP-0184",
-C.PI_TYPE: "XEP",
-C.PI_PROTOCOLS: ["XEP-0184"],
-C.PI_DEPENDENCIES: [],
-C.PI_MAIN: "XEP_0184",
-C.PI_HANDLER: "yes",
-C.PI_DESCRIPTION: _("""Implementation of Message Delivery Receipts""")
+    C.PI_NAME: "XEP-0184 Plugin",
+    C.PI_IMPORT_NAME: "XEP-0184",
+    C.PI_TYPE: "XEP",
+    C.PI_PROTOCOLS: ["XEP-0184"],
+    C.PI_DEPENDENCIES: [],
+    C.PI_MAIN: "XEP_0184",
+    C.PI_HANDLER: "yes",
+    C.PI_DESCRIPTION: _("""Implementation of Message Delivery Receipts"""),
 }
 
 
 STATUS_MESSAGE_DELIVERY_RECEIVED = "delivered"
-TEMPO_DELETE_WAITING_ACK_S = 300 # 5 min
+TEMPO_DELETE_WAITING_ACK_S = 300  # 5 min
 
 
 class XEP_0184(object):
     """
     Implementation for XEP 0184.
     """
+
     params = """
     <params>
     <individual>
@@ -79,10 +90,10 @@
     </individual>
     </params>
     """ % {
-        'category_name': PARAM_KEY,
-        'category_label': _(PARAM_KEY),
-        'param_name': PARAM_NAME,
-        'param_label': _('Enable message delivery receipts')
+        "category_name": PARAM_KEY,
+        "category_label": _(PARAM_KEY),
+        "param_name": PARAM_NAME,
+        "param_label": _("Enable message delivery receipts"),
     }
 
     def __init__(self, host):
@@ -94,24 +105,39 @@
         host.memory.updateParams(self.params)
 
         host.trigger.add("sendMessage", self.sendMessageTrigger)
-        host.bridge.addSignal("messageState", ".plugin", signature='sss')  # message_uid, status, profile
+        host.bridge.addSignal(
+            "messageState", ".plugin", signature="sss"
+        )  # message_uid, status, profile
 
     def getHandler(self, client):
         return XEP_0184_handler(self, client.profile)
 
-    def sendMessageTrigger(self, client, mess_data, pre_xml_treatments, post_xml_treatments):
+    def sendMessageTrigger(
+        self, client, mess_data, pre_xml_treatments, post_xml_treatments
+    ):
         """Install SendMessage command hook """
+
         def treatment(mess_data):
-            message = mess_data['xml']
+            message = mess_data["xml"]
             message_type = message.getAttribute("type")
 
-            if self._isActif(client.profile) and (message_type == "chat" or message_type == "normal"):
-                message.addElement('request', NS_MESSAGE_DELIVERY_RECEIPTS)
-                uid = mess_data['uid']
+            if self._isActif(client.profile) and (
+                message_type == "chat" or message_type == "normal"
+            ):
+                message.addElement("request", NS_MESSAGE_DELIVERY_RECEIPTS)
+                uid = mess_data["uid"]
                 msg_id = message.getAttribute("id")
                 self._dictRequest[msg_id] = uid
-                reactor.callLater(TEMPO_DELETE_WAITING_ACK_S, self._clearDictRequest, msg_id)
-                log.debug(_("[XEP-0184] Request acknowledgment for message id {}".format(msg_id)))
+                reactor.callLater(
+                    TEMPO_DELETE_WAITING_ACK_S, self._clearDictRequest, msg_id
+                )
+                log.debug(
+                    _(
+                        "[XEP-0184] Request acknowledgment for message id {}".format(
+                            msg_id
+                        )
+                    )
+                )
 
             return mess_data
 
@@ -122,13 +148,13 @@
         """This method is called on message delivery receipts **request** (XEP-0184 #7)
         @param msg_elt: message element
         @param client: %(doc_client)s"""
-        from_jid = jid.JID(msg_elt['from'])
+        from_jid = jid.JID(msg_elt["from"])
 
         if self._isActif(client.profile) and client.roster.isPresenceAuthorised(from_jid):
-            received_elt_ret = domish.Element((NS_MESSAGE_DELIVERY_RECEIPTS, 'received'))
+            received_elt_ret = domish.Element((NS_MESSAGE_DELIVERY_RECEIPTS, "received"))
             received_elt_ret["id"] = msg_elt["id"]
 
-            msg_result_elt = xmlstream.toResponse(msg_elt, 'result')
+            msg_result_elt = xmlstream.toResponse(msg_elt, "result")
             msg_result_elt.addChild(received_elt_ret)
             client.send(msg_result_elt)
 
@@ -137,27 +163,38 @@
         @param msg_elt: message element
         @param client: %(doc_client)s"""
         msg_elt.handled = True
-        rcv_elt = msg_elt.elements(NS_MESSAGE_DELIVERY_RECEIPTS, 'received').next()
-        msg_id = rcv_elt['id']
+        rcv_elt = msg_elt.elements(NS_MESSAGE_DELIVERY_RECEIPTS, "received").next()
+        msg_id = rcv_elt["id"]
 
         try:
             uid = self._dictRequest[msg_id]
             del self._dictRequest[msg_id]
-            self.host.bridge.messageState(uid, STATUS_MESSAGE_DELIVERY_RECEIVED, client.profile)
-            log.debug(_("[XEP-0184] Receive acknowledgment for message id {}".format(msg_id)))
+            self.host.bridge.messageState(
+                uid, STATUS_MESSAGE_DELIVERY_RECEIVED, client.profile
+            )
+            log.debug(
+                _("[XEP-0184] Receive acknowledgment for message id {}".format(msg_id))
+            )
         except KeyError:
             pass
 
     def _clearDictRequest(self, msg_id):
         try:
             del self._dictRequest[msg_id]
-            log.debug(_("[XEP-0184] Delete waiting acknowledgment for message id {}".format(msg_id)))
+            log.debug(
+                _(
+                    "[XEP-0184] Delete waiting acknowledgment for message id {}".format(
+                        msg_id
+                    )
+                )
+            )
         except KeyError:
             pass
 
     def _isActif(self, profile):
         return self.host.memory.getParamA(PARAM_NAME, PARAM_KEY, profile_key=profile)
 
+
 class XEP_0184_handler(XMPPHandler):
     implements(iwokkel.IDisco)
 
@@ -167,14 +204,30 @@
         self.profile = profile
 
     def connectionInitialized(self):
-        self.xmlstream.addObserver(MSG_CHAT_MESSAGE_DELIVERY_RECEIPTS_REQUEST, self.plugin_parent.onMessageDeliveryReceiptsRequest, client=self.parent)
-        self.xmlstream.addObserver(MSG_CHAT_MESSAGE_DELIVERY_RECEIPTS_RECEIVED, self.plugin_parent.onMessageDeliveryReceiptsReceived, client=self.parent)
+        self.xmlstream.addObserver(
+            MSG_CHAT_MESSAGE_DELIVERY_RECEIPTS_REQUEST,
+            self.plugin_parent.onMessageDeliveryReceiptsRequest,
+            client=self.parent,
+        )
+        self.xmlstream.addObserver(
+            MSG_CHAT_MESSAGE_DELIVERY_RECEIPTS_RECEIVED,
+            self.plugin_parent.onMessageDeliveryReceiptsReceived,
+            client=self.parent,
+        )
 
-        self.xmlstream.addObserver(MSG_NORMAL_MESSAGE_DELIVERY_RECEIPTS_REQUEST, self.plugin_parent.onMessageDeliveryReceiptsRequest, client=self.parent)
-        self.xmlstream.addObserver(MSG_NORMAL_MESSAGE_DELIVERY_RECEIPTS_RECEIVED, self.plugin_parent.onMessageDeliveryReceiptsReceived, client=self.parent)
+        self.xmlstream.addObserver(
+            MSG_NORMAL_MESSAGE_DELIVERY_RECEIPTS_REQUEST,
+            self.plugin_parent.onMessageDeliveryReceiptsRequest,
+            client=self.parent,
+        )
+        self.xmlstream.addObserver(
+            MSG_NORMAL_MESSAGE_DELIVERY_RECEIPTS_RECEIVED,
+            self.plugin_parent.onMessageDeliveryReceiptsReceived,
+            client=self.parent,
+        )
 
-    def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
+    def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
         return [disco.DiscoFeature(NS_MESSAGE_DELIVERY_RECEIPTS)]
 
-    def getDiscoItems(self, requestor, target, nodeIdentifier=''):
+    def getDiscoItems(self, requestor, target, nodeIdentifier=""):
         return []
--- a/sat/plugins/plugin_xep_0203.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_xep_0203.py	Wed Jun 27 20:14:46 2018 +0200
@@ -21,9 +21,11 @@
 from sat.core.i18n import _
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 
 from wokkel import disco, iwokkel, delay
+
 try:
     from twisted.words.protocols.xmlstream import XMPPHandler
 except ImportError:
@@ -31,7 +33,7 @@
 from zope.interface import implements
 
 
-NS_DD = 'urn:xmpp:delay'
+NS_DD = "urn:xmpp:delay"
 
 PLUGIN_INFO = {
     C.PI_NAME: "Delayed Delivery",
@@ -40,12 +42,11 @@
     C.PI_PROTOCOLS: ["XEP-0203"],
     C.PI_MAIN: "XEP_0203",
     C.PI_HANDLER: "yes",
-    C.PI_DESCRIPTION: _("""Implementation of Delayed Delivery""")
+    C.PI_DESCRIPTION: _("""Implementation of Delayed Delivery"""),
 }
 
 
 class XEP_0203(object):
-
     def __init__(self, host):
         log.info(_("Delayed Delivery plugin initialization"))
         self.host = host
@@ -53,7 +54,7 @@
     def getHandler(self, client):
         return XEP_0203_handler(self, client.profile)
 
-    def delay(self, stamp, sender=None, desc='', parent=None):
+    def delay(self, stamp, sender=None, desc="", parent=None):
         """Build a delay element, eventually append it to the given parent element.
 
         @param stamp (datetime): offset-aware timestamp of the original sending.
@@ -78,8 +79,8 @@
         self.host = plugin_parent.host
         self.profile = profile
 
-    def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
+    def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
         return [disco.DiscoFeature(NS_DD)]
 
-    def getDiscoItems(self, requestor, target, nodeIdentifier=''):
+    def getDiscoItems(self, requestor, target, nodeIdentifier=""):
         return []
--- a/sat/plugins/plugin_xep_0231.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_xep_0231.py	Wed Jun 27 20:14:46 2018 +0200
@@ -21,6 +21,7 @@
 from sat.core.constants import Const as C
 from sat.core import exceptions
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from sat.tools import xml_tools
 from wokkel import disco, iwokkel
@@ -43,24 +44,29 @@
     C.PI_PROTOCOLS: ["XEP-0231"],
     C.PI_MAIN: "XEP_0231",
     C.PI_HANDLER: "yes",
-    C.PI_DESCRIPTION: _("""Implementation of bits of binary (used for small images/files)""")
+    C.PI_DESCRIPTION: _(
+        """Implementation of bits of binary (used for small images/files)"""
+    ),
 }
 
-NS_BOB = u'urn:xmpp:bob'
+NS_BOB = u"urn:xmpp:bob"
 IQ_BOB_REQUEST = C.IQ_GET + '/data[@xmlns="' + NS_BOB + '"]'
 
 
 class XEP_0231(object):
-
     def __init__(self, host):
         log.info(_(u"plugin Bits of Binary initialization"))
         self.host = host
-        host.registerNamespace('bob', NS_BOB)
+        host.registerNamespace("bob", NS_BOB)
         host.trigger.add("xhtml_post_treat", self.XHTMLTrigger)
-        host.bridge.addMethod("bobGetFile", ".plugin",
-                              in_sign='sss', out_sign='s',
-                              method=self._getFile,
-                              async=True)
+        host.bridge.addMethod(
+            "bobGetFile",
+            ".plugin",
+            in_sign="sss",
+            out_sign="s",
+            method=self._getFile,
+            async=True,
+        )
 
     def dumpData(self, cache, data_elt, cid):
         """save file encoded in data_elt to cache
@@ -70,21 +76,19 @@
         @param cid(unicode): content-id
         @return(unicode): full path to dumped file
         """
-        # FIXME: is it needed to use a separate thread?
+        #  FIXME: is it needed to use a separate thread?
         #        probably not with the little data expected with BoB
         try:
-            max_age = int(data_elt['max-age'])
+            max_age = int(data_elt["max-age"])
             if max_age < 0:
                 raise ValueError
         except (KeyError, ValueError):
-            log.warning(u'invalid max-age found')
+            log.warning(u"invalid max-age found")
             max_age = None
 
         with cache.cacheData(
-            PLUGIN_INFO[C.PI_IMPORT_NAME],
-            cid,
-            data_elt.getAttribute('type'),
-            max_age) as f:
+            PLUGIN_INFO[C.PI_IMPORT_NAME], cid, data_elt.getAttribute("type"), max_age
+        ) as f:
 
             file_path = f.name
             f.write(base64.b64decode(str(data_elt)))
@@ -95,15 +99,16 @@
         return XEP_0231_handler(self)
 
     def _requestCb(self, iq_elt, cache, cid):
-        for data_elt in iq_elt.elements(NS_BOB, u'data'):
-            if data_elt.getAttribute('cid') == cid:
+        for data_elt in iq_elt.elements(NS_BOB, u"data"):
+            if data_elt.getAttribute("cid") == cid:
                 file_path = self.dumpData(cache, data_elt, cid)
                 return file_path
 
-        log.warning(u"invalid data stanza received, requested cid was not found:\n{iq_elt}\nrequested cid: {cid}".format(
-            iq_elt = iq_elt,
-            cid = cid
-            ))
+        log.warning(
+            u"invalid data stanza received, requested cid was not found:\n{iq_elt}\nrequested cid: {cid}".format(
+                iq_elt=iq_elt, cid=cid
+            )
+        )
         raise failure.Failure(exceptions.DataError("missing data"))
 
     def _requestEb(self, failure_):
@@ -122,39 +127,39 @@
         """
         if cache is None:
             cache = client.cache
-        iq_elt = client.IQ('get')
-        iq_elt['to'] = to_jid.full()
-        data_elt = iq_elt.addElement((NS_BOB, 'data'))
-        data_elt['cid'] = cid
+        iq_elt = client.IQ("get")
+        iq_elt["to"] = to_jid.full()
+        data_elt = iq_elt.addElement((NS_BOB, "data"))
+        data_elt["cid"] = cid
         d = iq_elt.send()
         d.addCallback(self._requestCb, cache, cid)
         d.addErrback(self._requestEb)
         return d
 
     def _setImgEltSrc(self, path, img_elt):
-        img_elt[u'src'] = u'file://{}'.format(path)
+        img_elt[u"src"] = u"file://{}".format(path)
 
     def XHTMLTrigger(self, client, message_elt, body_elt, lang, treat_d):
-        for img_elt in xml_tools.findAll(body_elt, C.NS_XHTML, u'img'):
-            source = img_elt.getAttribute(u'src','')
-            if source.startswith(u'cid:'):
+        for img_elt in xml_tools.findAll(body_elt, C.NS_XHTML, u"img"):
+            source = img_elt.getAttribute(u"src", "")
+            if source.startswith(u"cid:"):
                 cid = source[4:]
                 file_path = client.cache.getFilePath(cid)
                 if file_path is not None:
-                    # image is in cache, we change the url
-                    img_elt[u'src'] = u'file://{}'.format(file_path)
+                    #  image is in cache, we change the url
+                    img_elt[u"src"] = u"file://{}".format(file_path)
                     continue
                 else:
                     # image is not in cache, is it given locally?
-                    for data_elt in message_elt.elements(NS_BOB, u'data'):
-                        if data_elt.getAttribute('cid') == cid:
+                    for data_elt in message_elt.elements(NS_BOB, u"data"):
+                        if data_elt.getAttribute("cid") == cid:
                             file_path = self.dumpData(client.cache, data_elt, cid)
-                            img_elt[u'src'] = u'file://{}'.format(file_path)
+                            img_elt[u"src"] = u"file://{}".format(file_path)
                             break
                     else:
                         # cid not found locally, we need to request it
                         # so we use the deferred
-                        d = self.requestData(client, jid.JID(message_elt['from']), cid)
+                        d = self.requestData(client, jid.JID(message_elt["from"]), cid)
                         d.addCallback(partial(self._setImgEltSrc, img_elt=img_elt))
                         treat_d.addCallback(lambda dummy: d)
 
@@ -165,28 +170,28 @@
         #        An access check should be implemented though.
 
         iq_elt.handled = True
-        data_elt = next(iq_elt.elements(NS_BOB, 'data'))
+        data_elt = next(iq_elt.elements(NS_BOB, "data"))
         try:
-            cid = data_elt[u'cid']
+            cid = data_elt[u"cid"]
         except KeyError:
-            error_elt = jabber_error.StanzaError('not-acceptable').toResponse(iq_elt)
+            error_elt = jabber_error.StanzaError("not-acceptable").toResponse(iq_elt)
             client.send(error_elt)
             return
 
         metadata = self.host.common_cache.getMetadata(cid)
         if metadata is None:
-            error_elt = jabber_error.StanzaError('item-not-found').toResponse(iq_elt)
+            error_elt = jabber_error.StanzaError("item-not-found").toResponse(iq_elt)
             client.send(error_elt)
             return
 
-        with open(metadata['path']) as f:
+        with open(metadata["path"]) as f:
             data = f.read()
 
-        result_elt = xmlstream.toResponse(iq_elt, 'result')
-        data_elt = result_elt.addElement((NS_BOB, 'data'), content = data.encode('base64'))
-        data_elt[u'cid'] = cid
-        data_elt[u'type'] = metadata[u'mime_type']
-        data_elt[u'max-age'] = unicode(int(max(0, metadata['eol'] - time.time())))
+        result_elt = xmlstream.toResponse(iq_elt, "result")
+        data_elt = result_elt.addElement((NS_BOB, "data"), content=data.encode("base64"))
+        data_elt[u"cid"] = cid
+        data_elt[u"type"] = metadata[u"mime_type"]
+        data_elt[u"max-age"] = unicode(int(max(0, metadata["eol"] - time.time())))
         client.send(result_elt)
 
     def _getFile(self, peer_jid_s, cid, profile):
@@ -207,13 +212,13 @@
         """
         file_path = client.cache.getFilePath(cid)
         if file_path is not None:
-            # file is in cache
+            #  file is in cache
             return defer.succeed(file_path)
         else:
             # file not in cache, is it given locally?
             if parent_elt is not None:
-                for data_elt in parent_elt.elements(NS_BOB, u'data'):
-                    if data_elt.getAttribute('cid') == cid:
+                for data_elt in parent_elt.elements(NS_BOB, u"data"):
+                    if data_elt.getAttribute("cid") == cid:
                         return defer.succeed(self.dumpData(client.cache, data_elt, cid))
 
             # cid not found locally, we need to request it
@@ -230,10 +235,12 @@
 
     def connectionInitialized(self):
         if self.parent.is_component:
-            self.xmlstream.addObserver(IQ_BOB_REQUEST, self.plugin_parent.onComponentRequest, client=self.parent)
+            self.xmlstream.addObserver(
+                IQ_BOB_REQUEST, self.plugin_parent.onComponentRequest, client=self.parent
+            )
 
-    def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
+    def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
         return [disco.DiscoFeature(NS_BOB)]
 
-    def getDiscoItems(self, requestor, target, nodeIdentifier=''):
+    def getDiscoItems(self, requestor, target, nodeIdentifier=""):
         return []
--- a/sat/plugins/plugin_xep_0234.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_xep_0234.py	Wed Jun 27 20:14:46 2018 +0200
@@ -20,6 +20,7 @@
 from sat.core.i18n import _
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from sat.core import exceptions
 from wokkel import disco, iwokkel
@@ -40,7 +41,7 @@
 import mimetypes
 
 
-NS_JINGLE_FT = 'urn:xmpp:jingle:apps:file-transfer:5'
+NS_JINGLE_FT = "urn:xmpp:jingle:apps:file-transfer:5"
 
 PLUGIN_INFO = {
     C.PI_NAME: "Jingle File Transfer",
@@ -51,11 +52,11 @@
     C.PI_DEPENDENCIES: ["XEP-0166", "XEP-0300", "FILE"],
     C.PI_MAIN: "XEP_0234",
     C.PI_HANDLER: "yes",
-    C.PI_DESCRIPTION: _("""Implementation of Jingle File Transfer""")
+    C.PI_DESCRIPTION: _("""Implementation of Jingle File Transfer"""),
 }
 
-EXTRA_ALLOWED = {u'path', u'namespace', u'file_desc', u'file_hash'}
-Range = namedtuple('Range', ('offset', 'length'))
+EXTRA_ALLOWED = {u"path", u"namespace", u"file_desc", u"file_hash"}
+Range = namedtuple("Range", ("offset", "length"))
 
 
 class XEP_0234(object):
@@ -66,14 +67,30 @@
     def __init__(self, host):
         log.info(_("plugin Jingle File Transfer initialization"))
         self.host = host
-        host.registerNamespace('jingle-ft', NS_JINGLE_FT)
-        self._j = host.plugins["XEP-0166"] # shortcut to access jingle
+        host.registerNamespace("jingle-ft", NS_JINGLE_FT)
+        self._j = host.plugins["XEP-0166"]  # shortcut to access jingle
         self._j.registerApplication(NS_JINGLE_FT, self)
         self._f = host.plugins["FILE"]
-        self._f.register(NS_JINGLE_FT, self.fileJingleSend, priority = 10000, method_name=u"Jingle")
+        self._f.register(
+            NS_JINGLE_FT, self.fileJingleSend, priority=10000, method_name=u"Jingle"
+        )
         self._hash = self.host.plugins["XEP-0300"]
-        host.bridge.addMethod("fileJingleSend", ".plugin", in_sign='ssssa{ss}s', out_sign='', method=self._fileJingleSend, async=True)
-        host.bridge.addMethod("fileJingleRequest", ".plugin", in_sign='sssssa{ss}s', out_sign='s', method=self._fileJingleRequest, async=True)
+        host.bridge.addMethod(
+            "fileJingleSend",
+            ".plugin",
+            in_sign="ssssa{ss}s",
+            out_sign="",
+            method=self._fileJingleSend,
+            async=True,
+        )
+        host.bridge.addMethod(
+            "fileJingleRequest",
+            ".plugin",
+            in_sign="sssssa{ss}s",
+            out_sign="s",
+            method=self._fileJingleRequest,
+            async=True,
+        )
 
     def getHandler(self, client):
         return XEP_0234_handler()
@@ -85,12 +102,25 @@
         @param content_name(unicode): name of the content
         @return (unicode): unique progress id
         """
-        return u'{}_{}'.format(session['id'], content_name)
+        return u"{}_{}".format(session["id"], content_name)
 
     # generic methods
 
-    def buildFileElement(self, name, file_hash=None, hash_algo=None, size=None, mime_type=None, desc=None,
-                         modified=None, transfer_range=None, path=None, namespace=None, file_elt=None, **kwargs):
+    def buildFileElement(
+        self,
+        name,
+        file_hash=None,
+        hash_algo=None,
+        size=None,
+        mime_type=None,
+        desc=None,
+        modified=None,
+        transfer_range=None,
+        path=None,
+        namespace=None,
+        file_elt=None,
+        **kwargs
+    ):
         """Generate a <file> element with available metadata
 
         @param file_hash(unicode, None): hash of the file
@@ -109,26 +139,32 @@
         @trigger XEP-0234_buildFileElement(file_elt, extra_args): can be used to extend elements to add
         """
         if file_elt is None:
-            file_elt = domish.Element((NS_JINGLE_FT, u'file'))
-        for name, value in ((u'name', name), (u'size', size), ('media-type', mime_type),
-                            (u'desc', desc), (u'path', path), (u'namespace', namespace)):
+            file_elt = domish.Element((NS_JINGLE_FT, u"file"))
+        for name, value in (
+            (u"name", name),
+            (u"size", size),
+            ("media-type", mime_type),
+            (u"desc", desc),
+            (u"path", path),
+            (u"namespace", namespace),
+        ):
             if value is not None:
                 file_elt.addElement(name, content=unicode(value))
 
         if modified is not None:
             if isinstance(modified, int):
-                file_elt.addElement(u'date', utils.xmpp_date(modified or None))
+                file_elt.addElement(u"date", utils.xmpp_date(modified or None))
             else:
-                file_elt.addElement(u'date', modified)
-        elif 'created' in kwargs:
-            file_elt.addElement(u'date', utils.xmpp_date(kwargs.pop('created')))
+                file_elt.addElement(u"date", modified)
+        elif "created" in kwargs:
+            file_elt.addElement(u"date", utils.xmpp_date(kwargs.pop("created")))
 
-        range_elt = file_elt.addElement(u'range')
+        range_elt = file_elt.addElement(u"range")
         if transfer_range is not None:
             if transfer_range.offset is not None:
-                range_elt[u'offset'] = transfer_range.offset
+                range_elt[u"offset"] = transfer_range.offset
             if transfer_range.length is not None:
-                range_elt[u'length'] = transfer_range.length
+                range_elt[u"length"] = transfer_range.length
         if file_hash is not None:
             if not file_hash:
                 file_elt.addChild(self._hash.buildHashUsedElt())
@@ -136,10 +172,10 @@
                 file_elt.addChild(self._hash.buildHashElt(file_hash, hash_algo))
         elif hash_algo is not None:
             file_elt.addChild(self._hash.buildHashUsedElt(hash_algo))
-        self.host.trigger.point(u'XEP-0234_buildFileElement', file_elt, extra_args=kwargs)
+        self.host.trigger.point(u"XEP-0234_buildFileElement", file_elt, extra_args=kwargs)
         if kwargs:
             for kw in kwargs:
-                log.debug('ignored keyword: {}'.format(kw))
+                log.debug("ignored keyword: {}".format(kw))
         return file_elt
 
     def buildFileElementFromDict(self, file_data, **kwargs):
@@ -151,10 +187,16 @@
         if kwargs:
             file_data = file_data.copy()
             file_data.update(kwargs)
-        return self. buildFileElement(**file_data)
+        return self.buildFileElement(**file_data)
 
-
-    def parseFileElement(self, file_elt, file_data=None, given=False, parent_elt=None, keep_empty_range=False):
+    def parseFileElement(
+        self,
+        file_elt,
+        file_data=None,
+        given=False,
+        parent_elt=None,
+        keep_empty_range=False,
+    ):
         """Parse a <file> element and file dictionary accordingly
 
         @param file_data(dict, None): dict where the data will be set
@@ -173,77 +215,105 @@
         """
         if parent_elt is not None:
             if file_elt is not None:
-                raise exceptions.InternalError(u'file_elt must be None if parent_elt is set')
+                raise exceptions.InternalError(
+                    u"file_elt must be None if parent_elt is set"
+                )
             try:
-                file_elt = next(parent_elt.elements(NS_JINGLE_FT, u'file'))
+                file_elt = next(parent_elt.elements(NS_JINGLE_FT, u"file"))
             except StopIteration:
                 raise exceptions.NotFound()
         else:
             if not file_elt or file_elt.uri != NS_JINGLE_FT:
-                raise exceptions.DataError(u'invalid <file> element: {stanza}'.format(stanza = file_elt.toXml()))
+                raise exceptions.DataError(
+                    u"invalid <file> element: {stanza}".format(stanza=file_elt.toXml())
+                )
 
         if file_data is None:
             file_data = {}
 
-        for name in (u'name', u'desc',  u'path', u'namespace'):
+        for name in (u"name", u"desc", u"path", u"namespace"):
             try:
                 file_data[name] = unicode(next(file_elt.elements(NS_JINGLE_FT, name)))
             except StopIteration:
                 pass
 
-
-        name = file_data.get(u'name')
-        if name == u'..':
+        name = file_data.get(u"name")
+        if name == u"..":
             # we don't want to go to parent dir when joining to a path
-            name = u'--'
-            file_data[u'name'] = name
-        elif name is not None and u'/' in name or u'\\' in name:
-            file_data[u'name'] = regex.pathEscape(name)
+            name = u"--"
+            file_data[u"name"] = name
+        elif name is not None and u"/" in name or u"\\" in name:
+            file_data[u"name"] = regex.pathEscape(name)
 
         try:
-            file_data[u'mime_type'] = unicode(next(file_elt.elements(NS_JINGLE_FT, u'media-type')))
+            file_data[u"mime_type"] = unicode(
+                next(file_elt.elements(NS_JINGLE_FT, u"media-type"))
+            )
         except StopIteration:
             pass
 
         try:
-            file_data[u'size'] = int(unicode(next(file_elt.elements(NS_JINGLE_FT, u'size'))))
+            file_data[u"size"] = int(
+                unicode(next(file_elt.elements(NS_JINGLE_FT, u"size")))
+            )
         except StopIteration:
             pass
 
         try:
-            file_data[u'modified'] = date_utils.date_parse(next(file_elt.elements(NS_JINGLE_FT, u'date')))
+            file_data[u"modified"] = date_utils.date_parse(
+                next(file_elt.elements(NS_JINGLE_FT, u"date"))
+            )
         except StopIteration:
             pass
 
         try:
-            range_elt = file_elt.elements(NS_JINGLE_FT, u'range').next()
+            range_elt = file_elt.elements(NS_JINGLE_FT, u"range").next()
         except StopIteration:
             pass
         else:
-            offset = range_elt.getAttribute('offset')
-            length = range_elt.getAttribute('length')
+            offset = range_elt.getAttribute("offset")
+            length = range_elt.getAttribute("length")
             if offset or length or keep_empty_range:
-                file_data[u'transfer_range'] = Range(offset=offset, length=length)
+                file_data[u"transfer_range"] = Range(offset=offset, length=length)
 
-        prefix = u'given_' if given else u''
-        hash_algo_key, hash_key = u'hash_algo', prefix + u'file_hash'
+        prefix = u"given_" if given else u""
+        hash_algo_key, hash_key = u"hash_algo", prefix + u"file_hash"
         try:
-            file_data[hash_algo_key], file_data[hash_key] = self._hash.parseHashElt(file_elt)
+            file_data[hash_algo_key], file_data[hash_key] = self._hash.parseHashElt(
+                file_elt
+            )
         except exceptions.NotFound:
             pass
 
-        self.host.trigger.point(u'XEP-0234_parseFileElement', file_elt, file_data)
+        self.host.trigger.point(u"XEP-0234_parseFileElement", file_elt, file_data)
 
         return file_data
 
     # bridge methods
 
-    def _fileJingleSend(self, peer_jid, filepath, name="", file_desc="", extra=None, profile=C.PROF_KEY_NONE):
+    def _fileJingleSend(
+        self,
+        peer_jid,
+        filepath,
+        name="",
+        file_desc="",
+        extra=None,
+        profile=C.PROF_KEY_NONE,
+    ):
         client = self.host.getClient(profile)
-        return self.fileJingleSend(client, jid.JID(peer_jid), filepath, name or None, file_desc or None, extra or None)
+        return self.fileJingleSend(
+            client,
+            jid.JID(peer_jid),
+            filepath,
+            name or None,
+            file_desc or None,
+            extra or None,
+        )
 
     @defer.inlineCallbacks
-    def fileJingleSend(self, client, peer_jid, filepath, name, file_desc=None, extra=None):
+    def fileJingleSend(
+        self, client, peer_jid, filepath, name, file_desc=None, extra=None
+    ):
         """Send a file using jingle file transfer
 
         @param peer_jid(jid.JID): destinee jid
@@ -256,25 +326,58 @@
         if extra is None:
             extra = {}
         if file_desc is not None:
-            extra['file_desc'] = file_desc
-        yield self._j.initiate(client,
-                               peer_jid,
-                               [{'app_ns': NS_JINGLE_FT,
-                                 'senders': self._j.ROLE_INITIATOR,
-                                 'app_kwargs': {'filepath': filepath,
-                                                'name': name,
-                                                'extra': extra,
-                                                'progress_id_d': progress_id_d},
-                               }])
+            extra["file_desc"] = file_desc
+        yield self._j.initiate(
+            client,
+            peer_jid,
+            [
+                {
+                    "app_ns": NS_JINGLE_FT,
+                    "senders": self._j.ROLE_INITIATOR,
+                    "app_kwargs": {
+                        "filepath": filepath,
+                        "name": name,
+                        "extra": extra,
+                        "progress_id_d": progress_id_d,
+                    },
+                }
+            ],
+        )
         progress_id = yield progress_id_d
         defer.returnValue(progress_id)
 
-    def _fileJingleRequest(self, peer_jid, filepath, name="", file_hash="", hash_algo="", extra=None, profile=C.PROF_KEY_NONE):
+    def _fileJingleRequest(
+        self,
+        peer_jid,
+        filepath,
+        name="",
+        file_hash="",
+        hash_algo="",
+        extra=None,
+        profile=C.PROF_KEY_NONE,
+    ):
         client = self.host.getClient(profile)
-        return self.fileJingleRequest(client, jid.JID(peer_jid), filepath, name or None, file_hash or None, hash_algo or None, extra or None)
+        return self.fileJingleRequest(
+            client,
+            jid.JID(peer_jid),
+            filepath,
+            name or None,
+            file_hash or None,
+            hash_algo or None,
+            extra or None,
+        )
 
     @defer.inlineCallbacks
-    def fileJingleRequest(self, client, peer_jid, filepath, name=None, file_hash=None, hash_algo=None, extra=None):
+    def fileJingleRequest(
+        self,
+        client,
+        peer_jid,
+        filepath,
+        name=None,
+        file_hash=None,
+        hash_algo=None,
+        extra=None,
+    ):
         """Request a file using jingle file transfer
 
         @param peer_jid(jid.JID): destinee jid
@@ -289,122 +392,152 @@
         if file_hash is not None:
             if hash_algo is None:
                 raise ValueError(_(u"hash_algo must be set if file_hash is set"))
-            extra['file_hash'] = file_hash
-            extra['hash_algo'] = hash_algo
+            extra["file_hash"] = file_hash
+            extra["hash_algo"] = hash_algo
         else:
             if hash_algo is not None:
                 raise ValueError(_(u"file_hash must be set if hash_algo is set"))
-        yield self._j.initiate(client,
-                               peer_jid,
-                               [{'app_ns': NS_JINGLE_FT,
-                                 'senders': self._j.ROLE_RESPONDER,
-                                 'app_kwargs': {'filepath': filepath,
-                                                'name': name,
-                                                'extra': extra,
-                                                'progress_id_d': progress_id_d},
-                               }])
+        yield self._j.initiate(
+            client,
+            peer_jid,
+            [
+                {
+                    "app_ns": NS_JINGLE_FT,
+                    "senders": self._j.ROLE_RESPONDER,
+                    "app_kwargs": {
+                        "filepath": filepath,
+                        "name": name,
+                        "extra": extra,
+                        "progress_id_d": progress_id_d,
+                    },
+                }
+            ],
+        )
         progress_id = yield progress_id_d
         defer.returnValue(progress_id)
 
     # jingle callbacks
 
-    def jingleSessionInit(self, client, session, content_name, filepath, name, extra, progress_id_d):
+    def jingleSessionInit(
+        self, client, session, content_name, filepath, name, extra, progress_id_d
+    ):
         if extra is None:
             extra = {}
         else:
             if not EXTRA_ALLOWED.issuperset(extra):
-                raise ValueError(_(u"only the following keys are allowed in extra: {keys}").format(
-                    keys=u', '.join(EXTRA_ALLOWED)))
+                raise ValueError(
+                    _(u"only the following keys are allowed in extra: {keys}").format(
+                        keys=u", ".join(EXTRA_ALLOWED)
+                    )
+                )
         progress_id_d.callback(self.getProgressId(session, content_name))
-        content_data = session['contents'][content_name]
-        application_data = content_data['application_data']
-        assert 'file_path' not in application_data
-        application_data['file_path'] = filepath
-        file_data = application_data['file_data'] = {}
-        desc_elt = domish.Element((NS_JINGLE_FT, 'description'))
+        content_data = session["contents"][content_name]
+        application_data = content_data["application_data"]
+        assert "file_path" not in application_data
+        application_data["file_path"] = filepath
+        file_data = application_data["file_data"] = {}
+        desc_elt = domish.Element((NS_JINGLE_FT, "description"))
         file_elt = desc_elt.addElement("file")
 
-        if content_data[u'senders'] == self._j.ROLE_INITIATOR:
+        if content_data[u"senders"] == self._j.ROLE_INITIATOR:
             # we send a file
             if name is None:
                 name = os.path.basename(filepath)
-            file_data[u'date'] = utils.xmpp_date()
-            file_data[u'desc'] = extra.pop(u'file_desc', u'')
-            file_data[u'name'] = name
+            file_data[u"date"] = utils.xmpp_date()
+            file_data[u"desc"] = extra.pop(u"file_desc", u"")
+            file_data[u"name"] = name
             mime_type = mimetypes.guess_type(name, strict=False)[0]
             if mime_type is not None:
-                file_data[u'mime_type'] = mime_type
-            file_data[u'size'] = os.path.getsize(filepath)
-            if u'namespace' in extra:
-                file_data[u'namespace'] = extra[u'namespace']
-            if u'path' in extra:
-                file_data[u'path'] = extra[u'path']
-            self.buildFileElementFromDict(file_data, file_elt=file_elt, file_hash=u'')
+                file_data[u"mime_type"] = mime_type
+            file_data[u"size"] = os.path.getsize(filepath)
+            if u"namespace" in extra:
+                file_data[u"namespace"] = extra[u"namespace"]
+            if u"path" in extra:
+                file_data[u"path"] = extra[u"path"]
+            self.buildFileElementFromDict(file_data, file_elt=file_elt, file_hash=u"")
         else:
             # we request a file
-            file_hash = extra.pop(u'file_hash', u'')
+            file_hash = extra.pop(u"file_hash", u"")
             if not name and not file_hash:
-                raise ValueError(_(u'you need to provide at least name or file hash'))
+                raise ValueError(_(u"you need to provide at least name or file hash"))
             if name:
-                file_data[u'name'] = name
+                file_data[u"name"] = name
             if file_hash:
-                file_data[u'file_hash'] = file_hash
-                file_data[u'hash_algo'] = extra[u'hash_algo']
+                file_data[u"file_hash"] = file_hash
+                file_data[u"hash_algo"] = extra[u"hash_algo"]
             else:
-                file_data[u'hash_algo'] = self._hash.getDefaultAlgo()
-            if u'namespace' in extra:
-                file_data[u'namespace'] = extra[u'namespace']
-            if u'path' in extra:
-                file_data[u'path'] = extra[u'path']
+                file_data[u"hash_algo"] = self._hash.getDefaultAlgo()
+            if u"namespace" in extra:
+                file_data[u"namespace"] = extra[u"namespace"]
+            if u"path" in extra:
+                file_data[u"path"] = extra[u"path"]
             self.buildFileElementFromDict(file_data, file_elt=file_elt)
 
         return desc_elt
 
     def jingleRequestConfirmation(self, client, action, session, content_name, desc_elt):
         """This method request confirmation for a jingle session"""
-        content_data = session['contents'][content_name]
-        senders = content_data[u'senders']
+        content_data = session["contents"][content_name]
+        senders = content_data[u"senders"]
         if senders not in (self._j.ROLE_INITIATOR, self._j.ROLE_RESPONDER):
             log.warning(u"Bad sender, assuming initiator")
-            senders = content_data[u'senders'] = self._j.ROLE_INITIATOR
+            senders = content_data[u"senders"] = self._j.ROLE_INITIATOR
         # first we grab file informations
         try:
-            file_elt = desc_elt.elements(NS_JINGLE_FT, 'file').next()
+            file_elt = desc_elt.elements(NS_JINGLE_FT, "file").next()
         except StopIteration:
             raise failure.Failure(exceptions.DataError)
-        file_data = {'progress_id': self.getProgressId(session, content_name)}
+        file_data = {"progress_id": self.getProgressId(session, content_name)}
 
         if senders == self._j.ROLE_RESPONDER:
             # we send the file
-            return self._fileSendingRequestConf(client, session, content_data, content_name, file_data, file_elt)
+            return self._fileSendingRequestConf(
+                client, session, content_data, content_name, file_data, file_elt
+            )
         else:
             # we receive the file
-            return self._fileReceivingRequestConf(client, session, content_data, content_name, file_data, file_elt)
+            return self._fileReceivingRequestConf(
+                client, session, content_data, content_name, file_data, file_elt
+            )
 
     @defer.inlineCallbacks
-    def _fileSendingRequestConf(self, client, session, content_data, content_name, file_data, file_elt):
+    def _fileSendingRequestConf(
+        self, client, session, content_data, content_name, file_data, file_elt
+    ):
         """parse file_elt, and handle file retrieving/permission checking"""
         self.parseFileElement(file_elt, file_data)
-        content_data['application_data']['file_data'] = file_data
-        finished_d = content_data['finished_d'] = defer.Deferred()
+        content_data["application_data"]["file_data"] = file_data
+        finished_d = content_data["finished_d"] = defer.Deferred()
 
         # confirmed_d is a deferred returning confimed value (only used if cont is False)
-        cont, confirmed_d = self.host.trigger.returnPoint("XEP-0234_fileSendingRequest", client, session, content_data, content_name, file_data, file_elt)
+        cont, confirmed_d = self.host.trigger.returnPoint(
+            "XEP-0234_fileSendingRequest",
+            client,
+            session,
+            content_data,
+            content_name,
+            file_data,
+            file_elt,
+        )
         if not cont:
             confirmed = yield confirmed_d
             if confirmed:
                 args = [client, session, content_name, content_data]
-                finished_d.addCallbacks(self._finishedCb, self._finishedEb, args, None, args)
+                finished_d.addCallbacks(
+                    self._finishedCb, self._finishedEb, args, None, args
+                )
             defer.returnValue(confirmed)
 
-        log.warning(_(u'File continue is not implemented yet'))
+        log.warning(_(u"File continue is not implemented yet"))
         defer.returnValue(False)
 
-    def _fileReceivingRequestConf(self, client, session, content_data, content_name, file_data, file_elt):
+    def _fileReceivingRequestConf(
+        self, client, session, content_data, content_name, file_data, file_elt
+    ):
         """parse file_elt, and handle user permission/file opening"""
         self.parseFileElement(file_elt, file_data, given=True)
         try:
-            hash_algo, file_data['given_file_hash'] = self._hash.parseHashElt(file_elt)
+            hash_algo, file_data["given_file_hash"] = self._hash.parseHashElt(file_elt)
         except exceptions.NotFound:
             try:
                 hash_algo = self._hash.parseHashUsedElt(file_elt)
@@ -412,88 +545,94 @@
                 raise failure.Failure(exceptions.DataError)
 
         if hash_algo is not None:
-            file_data['hash_algo'] = hash_algo
-            file_data['hash_hasher'] = hasher = self._hash.getHasher(hash_algo)
-            file_data['data_cb'] = lambda data: hasher.update(data)
+            file_data["hash_algo"] = hash_algo
+            file_data["hash_hasher"] = hasher = self._hash.getHasher(hash_algo)
+            file_data["data_cb"] = lambda data: hasher.update(data)
 
         try:
-            file_data['size'] = int(file_data['size'])
+            file_data["size"] = int(file_data["size"])
         except ValueError:
             raise failure.Failure(exceptions.DataError)
 
-        name = file_data['name']
-        if '/' in name or '\\' in name:
-            log.warning(u"File name contain path characters, we replace them: {}".format(name))
-            file_data['name'] = name.replace('/', '_').replace('\\', '_')
+        name = file_data["name"]
+        if "/" in name or "\\" in name:
+            log.warning(
+                u"File name contain path characters, we replace them: {}".format(name)
+            )
+            file_data["name"] = name.replace("/", "_").replace("\\", "_")
 
-        content_data['application_data']['file_data'] = file_data
+        content_data["application_data"]["file_data"] = file_data
 
         # now we actualy request permission to user
         def gotConfirmation(confirmed):
             if confirmed:
                 args = [client, session, content_name, content_data]
-                finished_d.addCallbacks(self._finishedCb, self._finishedEb, args, None, args)
+                finished_d.addCallbacks(
+                    self._finishedCb, self._finishedEb, args, None, args
+                )
             return confirmed
 
         # deferred to track end of transfer
-        finished_d = content_data['finished_d'] = defer.Deferred()
-        d = self._f.getDestDir(client, session['peer_jid'], content_data, file_data, stream_object=True)
+        finished_d = content_data["finished_d"] = defer.Deferred()
+        d = self._f.getDestDir(
+            client, session["peer_jid"], content_data, file_data, stream_object=True
+        )
         d.addCallback(gotConfirmation)
         return d
 
     def jingleHandler(self, client, action, session, content_name, desc_elt):
-        content_data = session['contents'][content_name]
-        application_data = content_data['application_data']
+        content_data = session["contents"][content_name]
+        application_data = content_data["application_data"]
         if action in (self._j.A_ACCEPTED_ACK,):
             pass
         elif action == self._j.A_SESSION_INITIATE:
-            file_elt = desc_elt.elements(NS_JINGLE_FT, 'file').next()
+            file_elt = desc_elt.elements(NS_JINGLE_FT, "file").next()
             try:
-                file_elt.elements(NS_JINGLE_FT, 'range').next()
+                file_elt.elements(NS_JINGLE_FT, "range").next()
             except StopIteration:
                 # initiator doesn't manage <range>, but we do so we advertise it
-                # FIXME: to be checked
+                #  FIXME: to be checked
                 log.debug("adding <range> element")
-                file_elt.addElement('range')
+                file_elt.addElement("range")
         elif action == self._j.A_SESSION_ACCEPT:
-            assert not 'stream_object' in content_data
-            file_data = application_data['file_data']
-            file_path = application_data['file_path']
-            senders = content_data[u'senders']
-            if senders != session[u'role']:
+            assert not "stream_object" in content_data
+            file_data = application_data["file_data"]
+            file_path = application_data["file_path"]
+            senders = content_data[u"senders"]
+            if senders != session[u"role"]:
                 # we are receiving the file
                 try:
                     # did the responder specified the size of the file?
-                    file_elt = next(desc_elt.elements(NS_JINGLE_FT, u'file'))
-                    size_elt = next(file_elt.elements(NS_JINGLE_FT, u'size'))
+                    file_elt = next(desc_elt.elements(NS_JINGLE_FT, u"file"))
+                    size_elt = next(file_elt.elements(NS_JINGLE_FT, u"size"))
                     size = int(unicode(size_elt))
                 except (StopIteration, ValueError):
                     size = None
                 # XXX: hash security is not critical here, so we just take the higher mandatory one
-                hasher = file_data['hash_hasher'] = self._hash.getHasher()
-                content_data['stream_object'] = stream.FileStreamObject(
+                hasher = file_data["hash_hasher"] = self._hash.getHasher()
+                content_data["stream_object"] = stream.FileStreamObject(
                     self.host,
                     client,
                     file_path,
-                    mode='wb',
+                    mode="wb",
                     uid=self.getProgressId(session, content_name),
                     size=size,
                     data_cb=lambda data: hasher.update(data),
-                    )
+                )
             else:
                 # we are sending the file
-                size = file_data['size']
+                size = file_data["size"]
                 # XXX: hash security is not critical here, so we just take the higher mandatory one
-                hasher = file_data['hash_hasher'] = self._hash.getHasher()
-                content_data['stream_object'] = stream.FileStreamObject(
+                hasher = file_data["hash_hasher"] = self._hash.getHasher()
+                content_data["stream_object"] = stream.FileStreamObject(
                     self.host,
                     client,
                     file_path,
                     uid=self.getProgressId(session, content_name),
                     size=size,
                     data_cb=lambda data: hasher.update(data),
-                    )
-            finished_d = content_data['finished_d'] = defer.Deferred()
+                )
+            finished_d = content_data["finished_d"] = defer.Deferred()
             args = [client, session, content_name, content_data]
             finished_d.addCallbacks(self._finishedCb, self._finishedEb, args, None, args)
         else:
@@ -506,33 +645,42 @@
         manage checksum, and ignore <received/> element
         """
         # TODO: manage <received/> element
-        content_data = session['contents'][content_name]
+        content_data = session["contents"][content_name]
         elts = [elt for elt in jingle_elt.elements() if elt.uri == NS_JINGLE_FT]
         if not elts:
             return
         for elt in elts:
-            if elt.name == 'received':
+            if elt.name == "received":
                 pass
-            elif elt.name == 'checksum':
+            elif elt.name == "checksum":
                 # we have received the file hash, we need to parse it
-                if content_data['senders'] == session['role']:
-                    log.warning(u"unexpected checksum received while we are the file sender")
+                if content_data["senders"] == session["role"]:
+                    log.warning(
+                        u"unexpected checksum received while we are the file sender"
+                    )
                     raise exceptions.DataError
-                info_content_name = elt['name']
+                info_content_name = elt["name"]
                 if info_content_name != content_name:
                     # it was for an other content...
                     return
-                file_data = content_data['application_data']['file_data']
+                file_data = content_data["application_data"]["file_data"]
                 try:
-                    file_elt = elt.elements((NS_JINGLE_FT, 'file')).next()
+                    file_elt = elt.elements((NS_JINGLE_FT, "file")).next()
                 except StopIteration:
                     raise exceptions.DataError
-                algo, file_data['given_file_hash'] = self._hash.parseHashElt(file_elt)
-                if algo != file_data.get('hash_algo'):
-                    log.warning(u"Hash algorithm used in given hash ({peer_algo}) doesn't correspond to the one we have used ({our_algo}) [{profile}]"
-                        .format(peer_algo=algo, our_algo=file_data.get('hash_algo'), profile=client.profile))
+                algo, file_data["given_file_hash"] = self._hash.parseHashElt(file_elt)
+                if algo != file_data.get("hash_algo"):
+                    log.warning(
+                        u"Hash algorithm used in given hash ({peer_algo}) doesn't correspond to the one we have used ({our_algo}) [{profile}]".format(
+                            peer_algo=algo,
+                            our_algo=file_data.get("hash_algo"),
+                            profile=client.profile,
+                        )
+                    )
                 else:
-                    self._receiverTryTerminate(client, session, content_name, content_data)
+                    self._receiverTryTerminate(
+                        client, session, content_name, content_data
+                    )
             else:
                 raise NotImplementedError
 
@@ -540,23 +688,27 @@
         if jingle_elt.decline:
             # progress is the only way to tell to frontends that session has been declined
             progress_id = self.getProgressId(session, content_name)
-            self.host.bridge.progressError(progress_id, C.PROGRESS_ERROR_DECLINED, client.profile)
+            self.host.bridge.progressError(
+                progress_id, C.PROGRESS_ERROR_DECLINED, client.profile
+            )
 
     def _sendCheckSum(self, client, session, content_name, content_data):
         """Send the session-info with the hash checksum"""
-        file_data = content_data['application_data']['file_data']
-        hasher = file_data['hash_hasher']
+        file_data = content_data["application_data"]["file_data"]
+        hasher = file_data["hash_hasher"]
         hash_ = hasher.hexdigest()
         log.debug(u"Calculated hash: {}".format(hash_))
         iq_elt, jingle_elt = self._j.buildSessionInfo(client, session)
-        checksum_elt = jingle_elt.addElement((NS_JINGLE_FT, 'checksum'))
-        checksum_elt['creator'] = content_data['creator']
-        checksum_elt['name'] = content_name
-        file_elt = checksum_elt.addElement('file')
+        checksum_elt = jingle_elt.addElement((NS_JINGLE_FT, "checksum"))
+        checksum_elt["creator"] = content_data["creator"]
+        checksum_elt["name"] = content_name
+        file_elt = checksum_elt.addElement("file")
         file_elt.addChild(self._hash.buildHashElt(hash_))
         iq_elt.send()
 
-    def _receiverTryTerminate(self, client, session, content_name, content_data, last_try=False):
+    def _receiverTryTerminate(
+        self, client, session, content_name, content_data, last_try=False
+    ):
         """Try to terminate the session
 
         This method must only be used by the receiver.
@@ -565,70 +717,85 @@
         @param last_try(bool): if True this mean than session must be terminated even given hash is not available
         @return (bool): True if session was terminated
         """
-        if not content_data.get('transfer_finished', False):
+        if not content_data.get("transfer_finished", False):
             return False
-        file_data = content_data['application_data']['file_data']
-        given_hash = file_data.get('given_file_hash')
+        file_data = content_data["application_data"]["file_data"]
+        given_hash = file_data.get("given_file_hash")
         if given_hash is None:
             if last_try:
-                log.warning(u"sender didn't sent hash checksum, we can't check the file [{profile}]".format(profile=client.profile))
+                log.warning(
+                    u"sender didn't sent hash checksum, we can't check the file [{profile}]".format(
+                        profile=client.profile
+                    )
+                )
                 self._j.delayedContentTerminate(client, session, content_name)
-                content_data['stream_object'].close()
+                content_data["stream_object"].close()
                 return True
             return False
-        hasher = file_data['hash_hasher']
+        hasher = file_data["hash_hasher"]
         hash_ = hasher.hexdigest()
 
         if hash_ == given_hash:
             log.info(u"Hash checked, file was successfully transfered: {}".format(hash_))
-            progress_metadata = {'hash': hash_,
-                                 'hash_algo': file_data['hash_algo'],
-                                 'hash_verified': C.BOOL_TRUE
-                                }
+            progress_metadata = {
+                "hash": hash_,
+                "hash_algo": file_data["hash_algo"],
+                "hash_verified": C.BOOL_TRUE,
+            }
             error = None
         else:
             log.warning(u"Hash mismatch, the file was not transfered correctly")
-            progress_metadata=None
+            progress_metadata = None
             error = u"Hash mismatch: given={algo}:{given}, calculated={algo}:{our}".format(
-                algo = file_data['hash_algo'],
-                given = given_hash,
-                our = hash_)
+                algo=file_data["hash_algo"], given=given_hash, our=hash_
+            )
 
         self._j.delayedContentTerminate(client, session, content_name)
-        content_data['stream_object'].close(progress_metadata, error)
+        content_data["stream_object"].close(progress_metadata, error)
         # we may have the last_try timer still active, so we try to cancel it
         try:
-            content_data['last_try_timer'].cancel()
+            content_data["last_try_timer"].cancel()
         except (KeyError, internet_error.AlreadyCalled):
             pass
         return True
 
     def _finishedCb(self, dummy, client, session, content_name, content_data):
         log.info(u"File transfer terminated")
-        if content_data['senders'] != session['role']:
+        if content_data["senders"] != session["role"]:
             # we terminate the session only if we are the receiver,
             # as recommanded in XEP-0234 §2 (after example 6)
-            content_data['transfer_finished'] = True
-            if not self._receiverTryTerminate(client, session, content_name, content_data):
+            content_data["transfer_finished"] = True
+            if not self._receiverTryTerminate(
+                client, session, content_name, content_data
+            ):
                 # we have not received the hash yet, we wait 5 more seconds
-                content_data['last_try_timer'] = reactor.callLater(
-                    5, self._receiverTryTerminate, client, session, content_name, content_data, last_try=True)
+                content_data["last_try_timer"] = reactor.callLater(
+                    5,
+                    self._receiverTryTerminate,
+                    client,
+                    session,
+                    content_name,
+                    content_data,
+                    last_try=True,
+                )
         else:
             # we are the sender, we send the checksum
             self._sendCheckSum(client, session, content_name, content_data)
-            content_data['stream_object'].close()
+            content_data["stream_object"].close()
 
     def _finishedEb(self, failure, client, session, content_name, content_data):
         log.warning(u"Error while streaming file: {}".format(failure))
-        content_data['stream_object'].close()
-        self._j.contentTerminate(client, session, content_name, reason=self._j.REASON_FAILED_TRANSPORT)
+        content_data["stream_object"].close()
+        self._j.contentTerminate(
+            client, session, content_name, reason=self._j.REASON_FAILED_TRANSPORT
+        )
 
 
 class XEP_0234_handler(XMPPHandler):
     implements(iwokkel.IDisco)
 
-    def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
+    def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
         return [disco.DiscoFeature(NS_JINGLE_FT)]
 
-    def getDiscoItems(self, requestor, target, nodeIdentifier=''):
+    def getDiscoItems(self, requestor, target, nodeIdentifier=""):
         return []
--- a/sat/plugins/plugin_xep_0249.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_xep_0249.py	Wed Jun 27 20:14:46 2018 +0200
@@ -21,6 +21,7 @@
 from sat.core.constants import Const as C
 from sat.core import exceptions
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from sat.tools import xml_tools
 from twisted.words.xish import domish
@@ -36,8 +37,8 @@
 except ImportError:
     from wokkel.subprotocols import XMPPHandler
 
-MESSAGE = '/message'
-NS_DIRECT_MUC_INVITATION = 'jabber:x:conference'
+MESSAGE = "/message"
+NS_DIRECT_MUC_INVITATION = "jabber:x:conference"
 DIRECT_MUC_INVITATION_REQUEST = MESSAGE + '/x[@xmlns="' + NS_DIRECT_MUC_INVITATION + '"]'
 AUTOJOIN_KEY = "Misc"
 AUTOJOIN_NAME = "Auto-join MUC on invitation"
@@ -52,7 +53,7 @@
     C.PI_RECOMMENDATIONS: [C.TEXT_CMDS],
     C.PI_MAIN: "XEP_0249",
     C.PI_HANDLER: "yes",
-    C.PI_DESCRIPTION: _("""Implementation of Direct MUC Invitations""")
+    C.PI_DESCRIPTION: _("""Implementation of Direct MUC Invitations"""),
 }
 
 
@@ -69,20 +70,26 @@
     </individual>
     </params>
     """ % {
-        'category_name': AUTOJOIN_KEY,
-        'category_label': _("Misc"),
-        'param_name': AUTOJOIN_NAME,
-        'param_label': _("Auto-join MUC on invitation"),
-        'param_options': '\n'.join(['<option value="%s" %s/>' % \
-                                   (value, 'selected="true"' if value == AUTOJOIN_VALUES[0] else '') \
-                                   for value in AUTOJOIN_VALUES])
+        "category_name": AUTOJOIN_KEY,
+        "category_label": _("Misc"),
+        "param_name": AUTOJOIN_NAME,
+        "param_label": _("Auto-join MUC on invitation"),
+        "param_options": "\n".join(
+            [
+                '<option value="%s" %s/>'
+                % (value, 'selected="true"' if value == AUTOJOIN_VALUES[0] else "")
+                for value in AUTOJOIN_VALUES
+            ]
+        ),
     }
 
     def __init__(self, host):
         log.info(_("Plugin XEP_0249 initialization"))
         self.host = host
         host.memory.updateParams(self.params)
-        host.bridge.addMethod("inviteMUC", ".plugin", in_sign='ssa{ss}s', out_sign='', method=self._invite)
+        host.bridge.addMethod(
+            "inviteMUC", ".plugin", in_sign="ssa{ss}s", out_sign="", method=self._invite
+        )
         try:
             self.host.plugins[C.TEXT_CMDS].registerTextCommands(self)
         except KeyError:
@@ -99,7 +106,7 @@
         @param roomId: name of the room
         @param profile_key: %(doc_profile_key)s
         """
-        #TODO: check parameters validity
+        # TODO: check parameters validity
         client = self.host.getClient(profile_key)
         self.invite(client, jid.JID(guest_jid_s), jid.JID(room_jid_s, options))
 
@@ -110,16 +117,16 @@
         @param room(jid.JID): jid of the room where the user is invited
         @param options(dict): attribute with extra info (reason, password) as in #XEP-0249
         """
-        message = domish.Element((None, 'message'))
+        message = domish.Element((None, "message"))
         message["to"] = guest.full()
-        x_elt = message.addElement((NS_DIRECT_MUC_INVITATION, 'x'))
-        x_elt['jid'] = room.userhost()
+        x_elt = message.addElement((NS_DIRECT_MUC_INVITATION, "x"))
+        x_elt["jid"] = room.userhost()
         for key, value in options.iteritems():
-            if key not in ('password', 'reason', 'thread'):
+            if key not in ("password", "reason", "thread"):
                 log.warning(u"Ignoring invalid invite option: {}".format(key))
                 continue
             x_elt[key] = value
-        # there is not body in this message, so we can use directly send()
+        #  there is not body in this message, so we can use directly send()
         client.send(message)
 
     def _accept(self, room_jid, profile_key=C.PROF_KEY_NONE):
@@ -128,7 +135,10 @@
         @param room (jid.JID): JID of the room
         """
         client = self.host.getClient(profile_key)
-        log.info(_(u'Invitation accepted for room %(room)s [%(profile)s]') % {'room': room_jid.userhost(), 'profile': client.profile})
+        log.info(
+            _(u"Invitation accepted for room %(room)s [%(profile)s]")
+            % {"room": room_jid.userhost(), "profile": client.profile}
+        )
         d = self.host.plugins["XEP-0045"].join(client, room_jid, client.jid.user, {})
         return d
 
@@ -140,10 +150,13 @@
         """
         client = self.host.getClient(profile)
         try:
-            room_jid_s = message.firstChildElement()['jid']
-            log.info(_(u'Invitation received for room %(room)s [%(profile)s]') % {'room': room_jid_s, 'profile': profile})
+            room_jid_s = message.firstChildElement()["jid"]
+            log.info(
+                _(u"Invitation received for room %(room)s [%(profile)s]")
+                % {"room": room_jid_s, "profile": profile}
+            )
         except:
-            log.error(_('Error while parsing invitation'))
+            log.error(_("Error while parsing invitation"))
             return
         from_jid_s = message["from"]
         room_jid = jid.JID(room_jid_s)
@@ -152,21 +165,32 @@
         except exceptions.NotFound:
             pass
         else:
-            log.info(_(u"Invitation silently discarded because user is already in the room."))
+            log.info(
+                _(u"Invitation silently discarded because user is already in the room.")
+            )
             return
 
-        autojoin = self.host.memory.getParamA(AUTOJOIN_NAME, AUTOJOIN_KEY, profile_key=profile)
+        autojoin = self.host.memory.getParamA(
+            AUTOJOIN_NAME, AUTOJOIN_KEY, profile_key=profile
+        )
 
         if autojoin == "always":
             self._accept(room_jid, profile)
         elif autojoin == "never":
-            msg = D_("An invitation from %(user)s to join the room %(room)s has been declined according to your personal settings.") % {'user': from_jid_s, 'room': room_jid_s}
+            msg = D_(
+                "An invitation from %(user)s to join the room %(room)s has been declined according to your personal settings."
+            ) % {"user": from_jid_s, "room": room_jid_s}
             title = D_("MUC invitation")
             xml_tools.quickNote(self.host, client, msg, title, C.XMLUI_DATA_LVL_INFO)
         else:  # leave the default value here
-            confirm_msg = D_("You have been invited by %(user)s to join the room %(room)s. Do you accept?") % {'user': from_jid_s, 'room': room_jid_s}
+            confirm_msg = D_(
+                "You have been invited by %(user)s to join the room %(room)s. Do you accept?"
+            ) % {"user": from_jid_s, "room": room_jid_s}
             confirm_title = D_("MUC invitation")
-            d = xml_tools.deferConfirm(self.host, confirm_msg, confirm_title, profile=profile)
+            d = xml_tools.deferConfirm(
+                self.host, confirm_msg, confirm_title, profile=profile
+            )
+
             def accept_cb(accepted):
                 if accepted:
                     self._accept(room_jid, profile)
@@ -184,7 +208,9 @@
         try:
             contact_jid = jid.JID(contact_jid_s)
         except (RuntimeError, jid.InvalidFormat, AttributeError):
-            feedback = _(u"You must provide a valid JID to invite, like in '/invite contact@{host}'").format(host=my_host)
+            feedback = _(
+                u"You must provide a valid JID to invite, like in '/invite contact@{host}'"
+            ).format(host=my_host)
             self.host.plugins[C.TEXT_CMDS].feedBack(client, feedback, mess_data)
             return False
         if not contact_jid.user:
@@ -201,10 +227,14 @@
         self.host = plugin_parent.host
 
     def connectionInitialized(self):
-        self.xmlstream.addObserver(DIRECT_MUC_INVITATION_REQUEST, self.plugin_parent.onInvitation, profile=self.parent.profile)
+        self.xmlstream.addObserver(
+            DIRECT_MUC_INVITATION_REQUEST,
+            self.plugin_parent.onInvitation,
+            profile=self.parent.profile,
+        )
 
-    def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
+    def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
         return [disco.DiscoFeature(NS_DIRECT_MUC_INVITATION)]
 
-    def getDiscoItems(self, requestor, target, nodeIdentifier=''):
+    def getDiscoItems(self, requestor, target, nodeIdentifier=""):
         return []
--- a/sat/plugins/plugin_xep_0260.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_xep_0260.py	Wed Jun 27 20:14:46 2018 +0200
@@ -20,6 +20,7 @@
 from sat.core.i18n import _
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from sat.core import exceptions
 from wokkel import disco, iwokkel
@@ -35,7 +36,7 @@
     from wokkel.subprotocols import XMPPHandler
 
 
-NS_JINGLE_S5B = 'urn:xmpp:jingle:transports:s5b:1'
+NS_JINGLE_S5B = "urn:xmpp:jingle:transports:s5b:1"
 
 PLUGIN_INFO = {
     C.PI_NAME: "Jingle SOCKS5 Bytestreams",
@@ -44,15 +45,14 @@
     C.PI_MODES: C.PLUG_MODE_BOTH,
     C.PI_PROTOCOLS: ["XEP-0260"],
     C.PI_DEPENDENCIES: ["XEP-0166", "XEP-0065"],
-    C.PI_RECOMMENDATIONS: ["XEP-0261"], # needed for fallback
+    C.PI_RECOMMENDATIONS: ["XEP-0261"],  # needed for fallback
     C.PI_MAIN: "XEP_0260",
     C.PI_HANDLER: "yes",
-    C.PI_DESCRIPTION: _("""Implementation of Jingle SOCKS5 Bytestreams""")
+    C.PI_DESCRIPTION: _("""Implementation of Jingle SOCKS5 Bytestreams"""),
 }
 
 
 class ProxyError(Exception):
-
     def __str__(self):
         return "an error happened while trying to use the proxy"
 
@@ -63,8 +63,8 @@
     def __init__(self, host):
         log.info(_("plugin Jingle SOCKS5 Bytestreams"))
         self.host = host
-        self._j = host.plugins["XEP-0166"] # shortcut to access jingle
-        self._s5b = host.plugins["XEP-0065"] # and socks5 bytestream
+        self._j = host.plugins["XEP-0166"]  # shortcut to access jingle
+        self._s5b = host.plugins["XEP-0065"]  # and socks5 bytestream
         try:
             self._jingle_ibb = host.plugins["XEP-0261"]
         except KeyError:
@@ -81,14 +81,14 @@
         @return (list[plugin_xep_0065.Candidate): list of parsed candidates
         """
         candidates = []
-        for candidate_elt in transport_elt.elements(NS_JINGLE_S5B, 'candidate'):
+        for candidate_elt in transport_elt.elements(NS_JINGLE_S5B, "candidate"):
             try:
-                cid = candidate_elt['cid']
-                host = candidate_elt['host']
-                jid_= jid.JID(candidate_elt['jid'])
-                port = int(candidate_elt.getAttribute('port', 1080))
-                priority = int(candidate_elt['priority'])
-                type_ = candidate_elt.getAttribute('type', self._s5b.TYPE_DIRECT)
+                cid = candidate_elt["cid"]
+                host = candidate_elt["host"]
+                jid_ = jid.JID(candidate_elt["jid"])
+                port = int(candidate_elt.getAttribute("port", 1080))
+                priority = int(candidate_elt["priority"])
+                type_ = candidate_elt.getAttribute("type", self._s5b.TYPE_DIRECT)
             except (KeyError, ValueError):
                 raise exceptions.DataError()
             candidate = self._s5b.Candidate(host, port, type_, priority, jid_, cid)
@@ -106,38 +106,51 @@
         @param mode(str, None): 'tcp' or 'udp', or None to have no attribute
         @return (domish.Element): parent <transport> element where <candidate> elements must be added
         """
-        proxy = next((candidate for candidate in candidates if candidate.type == self._s5b.TYPE_PROXY), None)
+        proxy = next(
+            (
+                candidate
+                for candidate in candidates
+                if candidate.type == self._s5b.TYPE_PROXY
+            ),
+            None,
+        )
         transport_elt = domish.Element((NS_JINGLE_S5B, "transport"))
-        transport_elt['sid'] = sid
+        transport_elt["sid"] = sid
         if proxy is not None:
-            transport_elt['dstaddr'] = session_hash
+            transport_elt["dstaddr"] = session_hash
         if mode is not None:
-            transport_elt['mode'] = 'tcp' # XXX: we only manage tcp for now
+            transport_elt["mode"] = "tcp"  # XXX: we only manage tcp for now
 
         for candidate in candidates:
             log.debug(u"Adding candidate: {}".format(candidate))
-            candidate_elt = transport_elt.addElement('candidate', NS_JINGLE_S5B)
+            candidate_elt = transport_elt.addElement("candidate", NS_JINGLE_S5B)
             if candidate.id is None:
                 candidate.id = unicode(uuid.uuid4())
-            candidate_elt['cid'] = candidate.id
-            candidate_elt['host'] = candidate.host
-            candidate_elt['jid'] = candidate.jid.full()
-            candidate_elt['port'] = unicode(candidate.port)
-            candidate_elt['priority'] = unicode(candidate.priority)
-            candidate_elt['type'] = candidate.type
+            candidate_elt["cid"] = candidate.id
+            candidate_elt["host"] = candidate.host
+            candidate_elt["jid"] = candidate.jid.full()
+            candidate_elt["port"] = unicode(candidate.port)
+            candidate_elt["priority"] = unicode(candidate.priority)
+            candidate_elt["type"] = candidate.type
         return transport_elt
 
     @defer.inlineCallbacks
     def jingleSessionInit(self, client, session, content_name):
-        content_data = session['contents'][content_name]
-        transport_data = content_data['transport_data']
-        sid = transport_data['sid'] = unicode(uuid.uuid4())
-        session_hash = transport_data['session_hash'] = self._s5b.getSessionHash(client.jid, session['peer_jid'], sid)
-        transport_data['peer_session_hash'] = self._s5b.getSessionHash(session['peer_jid'], client.jid, sid) # requester and target are inversed for peer candidates
-        transport_data['stream_d'] = self._s5b.registerHash(client, session_hash, None)
-        candidates = transport_data['candidates'] = yield self._s5b.getCandidates(client)
-        mode = 'tcp' # XXX: we only manage tcp for now
-        transport_elt = self._buildCandidates(session, candidates, sid, session_hash, client, mode)
+        content_data = session["contents"][content_name]
+        transport_data = content_data["transport_data"]
+        sid = transport_data["sid"] = unicode(uuid.uuid4())
+        session_hash = transport_data["session_hash"] = self._s5b.getSessionHash(
+            client.jid, session["peer_jid"], sid
+        )
+        transport_data["peer_session_hash"] = self._s5b.getSessionHash(
+            session["peer_jid"], client.jid, sid
+        )  # requester and target are inversed for peer candidates
+        transport_data["stream_d"] = self._s5b.registerHash(client, session_hash, None)
+        candidates = transport_data["candidates"] = yield self._s5b.getCandidates(client)
+        mode = "tcp"  # XXX: we only manage tcp for now
+        transport_elt = self._buildCandidates(
+            session, candidates, sid, session_hash, client, mode
+        )
 
         defer.returnValue(transport_elt)
 
@@ -147,9 +160,11 @@
         cf XEP-0260 § 2.4
         """
         # now that the proxy is activated, we have to inform other peer
-        iq_elt, transport_elt = self._j.buildAction(client, self._j.A_TRANSPORT_INFO, session, content_name)
-        activated_elt = transport_elt.addElement('activated')
-        activated_elt['cid'] = candidate.id
+        iq_elt, transport_elt = self._j.buildAction(
+            client, self._j.A_TRANSPORT_INFO, session, content_name
+        )
+        activated_elt = transport_elt.addElement("activated")
+        activated_elt["cid"] = candidate.id
         iq_elt.send()
 
     def _proxyActivatedEb(self, stanza_error, client, candidate, session, content_name):
@@ -159,14 +174,21 @@
         """
         # TODO: fallback to IBB
         # now that the proxy is activated, we have to inform other peer
-        iq_elt, transport_elt = self._j.buildAction(client, self._j.A_TRANSPORT_INFO, session, content_name)
-        transport_elt.addElement('proxy-error')
+        iq_elt, transport_elt = self._j.buildAction(
+            client, self._j.A_TRANSPORT_INFO, session, content_name
+        )
+        transport_elt.addElement("proxy-error")
         iq_elt.send()
-        log.warning(u"Can't activate proxy, we need to fallback to IBB: {reason}"
-            .format(reason = stanza_error.value.condition))
+        log.warning(
+            u"Can't activate proxy, we need to fallback to IBB: {reason}".format(
+                reason=stanza_error.value.condition
+            )
+        )
         self.doFallback(session, content_name, client)
 
-    def _foundPeerCandidate(self, candidate, session, transport_data, content_name, client):
+    def _foundPeerCandidate(
+        self, candidate, session, transport_data, content_name, client
+    ):
         """Called when the best candidate from other peer is found
 
         @param candidate(XEP_0065.Candidate, None): selected candidate,
@@ -177,22 +199,24 @@
         @param client(unicode): %(doc_client)s
         """
 
-        transport_data['best_candidate'] = candidate
+        transport_data["best_candidate"] = candidate
         # we need to disconnect all non selected candidates before removing them
-        for c in transport_data['peer_candidates']:
+        for c in transport_data["peer_candidates"]:
             if c is None or c is candidate:
                 continue
             c.discard()
-        del transport_data['peer_candidates']
-        iq_elt, transport_elt = self._j.buildAction(client, self._j.A_TRANSPORT_INFO, session, content_name)
+        del transport_data["peer_candidates"]
+        iq_elt, transport_elt = self._j.buildAction(
+            client, self._j.A_TRANSPORT_INFO, session, content_name
+        )
         if candidate is None:
             log.warning(u"Can't connect to any peer candidate")
-            candidate_elt = transport_elt.addElement('candidate-error')
+            candidate_elt = transport_elt.addElement("candidate-error")
         else:
             log.info(u"Found best peer candidate: {}".format(unicode(candidate)))
-            candidate_elt = transport_elt.addElement('candidate-used')
-            candidate_elt['cid'] = candidate.id
-        iq_elt.send() # TODO: check result stanza
+            candidate_elt = transport_elt.addElement("candidate-used")
+            candidate_elt["cid"] = candidate.id
+        iq_elt.send()  # TODO: check result stanza
         self._checkCandidates(session, content_name, transport_data, client)
 
     def _checkCandidates(self, session, content_name, transport_data, client):
@@ -204,14 +228,14 @@
         @param transport_data(dict): transport data
         @param client(unicode): %(doc_client)s
         """
-        content_data = session['contents'][content_name]
+        content_data = session["contents"][content_name]
         try:
-            best_candidate = transport_data['best_candidate']
+            best_candidate = transport_data["best_candidate"]
         except KeyError:
             # we have not our best candidate yet
             return
         try:
-            peer_best_candidate = transport_data['peer_best_candidate']
+            peer_best_candidate = transport_data["peer_best_candidate"]
         except KeyError:
             # we have not peer best candidate yet
             return
@@ -222,13 +246,17 @@
         else:
             if best_candidate.priority == peer_best_candidate.priority:
                 # same priority, we choose initiator one according to XEP-0260 §2.4 #4
-                log.debug(u"Candidates have same priority, we select the one choosed by initiator")
-                if session['initiator'] == client.jid:
+                log.debug(
+                    u"Candidates have same priority, we select the one choosed by initiator"
+                )
+                if session["initiator"] == client.jid:
                     choosed_candidate = best_candidate
                 else:
                     choosed_candidate = peer_best_candidate
             else:
-                choosed_candidate = max(best_candidate, peer_best_candidate, key=lambda c:c.priority)
+                choosed_candidate = max(
+                    best_candidate, peer_best_candidate, key=lambda c: c.priority
+                )
 
         if choosed_candidate is None:
             log.warning(u"Socks5 negociation failed, we need to fallback to IBB")
@@ -241,34 +269,49 @@
                 # than also mean that best_candidate must be discarded !
                 try:
                     best_candidate.discard()
-                except AttributeError: # but it can be None
+                except AttributeError:  # but it can be None
                     pass
             else:
                 our_candidate = False
 
-            log.info(u"Socks5 negociation successful, {who} candidate will be used: {candidate}".format(
-                who = u'our' if our_candidate else u'other peer',
-                candidate = choosed_candidate))
-            del transport_data['best_candidate']
-            del transport_data['peer_best_candidate']
+            log.info(
+                u"Socks5 negociation successful, {who} candidate will be used: {candidate}".format(
+                    who=u"our" if our_candidate else u"other peer",
+                    candidate=choosed_candidate,
+                )
+            )
+            del transport_data["best_candidate"]
+            del transport_data["peer_best_candidate"]
 
             if choosed_candidate.type == self._s5b.TYPE_PROXY:
                 # the stream transfer need to wait for proxy activation
                 # (see XEP-0260 § 2.4)
                 if our_candidate:
-                    d = self._s5b.connectCandidate(client, choosed_candidate, transport_data['session_hash'])
-                    d.addCallback(lambda dummy: choosed_candidate.activate(transport_data['sid'], session['peer_jid'], client))
+                    d = self._s5b.connectCandidate(
+                        client, choosed_candidate, transport_data["session_hash"]
+                    )
+                    d.addCallback(
+                        lambda dummy: choosed_candidate.activate(
+                            transport_data["sid"], session["peer_jid"], client
+                        )
+                    )
                     args = [client, choosed_candidate, session, content_name]
-                    d.addCallbacks(self._proxyActivatedCb, self._proxyActivatedEb, args, None, args)
+                    d.addCallbacks(
+                        self._proxyActivatedCb, self._proxyActivatedEb, args, None, args
+                    )
                 else:
                     # this Deferred will be called when we'll receive activation confirmation from other peer
-                    d = transport_data['activation_d'] = defer.Deferred()
+                    d = transport_data["activation_d"] = defer.Deferred()
             else:
                 d = defer.succeed(None)
 
-            if content_data['senders'] == session['role']:
+            if content_data["senders"] == session["role"]:
                 # we can now start the stream transfer (or start it after proxy activation)
-                d.addCallback(lambda dummy: choosed_candidate.startTransfer(transport_data['session_hash']))
+                d.addCallback(
+                    lambda dummy: choosed_candidate.startTransfer(
+                        transport_data["session_hash"]
+                    )
+                )
                 d.addErrback(self._startEb, session, content_name, client)
 
     def _startEb(self, fail, session, content_name, client):
@@ -283,7 +326,9 @@
         log.warning(u"Cant start transfert, we'll try fallback method: {}".format(reason))
         self.doFallback(session, content_name, client)
 
-    def _candidateInfo(self, candidate_elt, session, content_name, transport_data, client):
+    def _candidateInfo(
+        self, candidate_elt, session, content_name, transport_data, client
+    ):
         """Called when best candidate has been received from peer (or if none is working)
 
         @param candidate_elt(domish.Element): candidate-used or candidate-error element
@@ -293,34 +338,40 @@
         @param transport_data(dict): transport data
         @param client(unicode): %(doc_client)s
         """
-        if candidate_elt.name == 'candidate-error':
+        if candidate_elt.name == "candidate-error":
             # candidate-error, no candidate worked
-            transport_data['peer_best_candidate'] = None
+            transport_data["peer_best_candidate"] = None
         else:
             # candidate-used, one candidate was choosed
             try:
-                cid = candidate_elt.attributes['cid']
+                cid = candidate_elt.attributes["cid"]
             except KeyError:
                 log.warning(u"No cid found in <candidate-used>")
                 raise exceptions.DataError
             try:
-                candidate = (c for c in transport_data['candidates'] if c.id == cid).next()
+                candidate = (
+                    c for c in transport_data["candidates"] if c.id == cid
+                ).next()
             except StopIteration:
                 log.warning(u"Given cid doesn't correspond to any known candidate !")
-                raise exceptions.DataError # TODO: send an error to other peer, and use better exception
+                raise exceptions.DataError  # TODO: send an error to other peer, and use better exception
             except KeyError:
                 # a transport-info can also be intentionaly sent too early by other peer
                 # but there is little probability
-                log.error(u'"candidates" key doesn\'t exists in transport_data, it should at this point')
+                log.error(
+                    u'"candidates" key doesn\'t exists in transport_data, it should at this point'
+                )
                 raise exceptions.InternalError
             # at this point we have the candidate choosed by other peer
-            transport_data['peer_best_candidate'] = candidate
+            transport_data["peer_best_candidate"] = candidate
             log.info(u"Other peer best candidate: {}".format(candidate))
 
-        del transport_data['candidates']
+        del transport_data["candidates"]
         self._checkCandidates(session, content_name, transport_data, client)
 
-    def _proxyActivationInfo(self, proxy_elt, session, content_name, transport_data, client):
+    def _proxyActivationInfo(
+        self, proxy_elt, session, content_name, transport_data, client
+    ):
         """Called when proxy has been activated (or has sent an error)
 
         @param proxy_elt(domish.Element): <activated/> or <proxy-error/> element
@@ -331,81 +382,107 @@
         @param client(unicode): %(doc_client)s
         """
         try:
-            activation_d = transport_data.pop('activation_d')
+            activation_d = transport_data.pop("activation_d")
         except KeyError:
             log.warning(u"Received unexpected transport-info for proxy activation")
 
-        if proxy_elt.name == 'activated':
+        if proxy_elt.name == "activated":
             activation_d.callback(None)
         else:
             activation_d.errback(ProxyError())
 
     @defer.inlineCallbacks
     def jingleHandler(self, client, action, session, content_name, transport_elt):
-        content_data = session['contents'][content_name]
-        transport_data = content_data['transport_data']
+        content_data = session["contents"][content_name]
+        transport_data = content_data["transport_data"]
 
         if action in (self._j.A_ACCEPTED_ACK, self._j.A_PREPARE_RESPONDER):
             pass
 
         elif action == self._j.A_SESSION_ACCEPT:
             # initiator side, we select a candidate in the ones sent by responder
-            assert 'peer_candidates' not in transport_data
-            transport_data['peer_candidates'] = self._parseCandidates(transport_elt)
+            assert "peer_candidates" not in transport_data
+            transport_data["peer_candidates"] = self._parseCandidates(transport_elt)
 
         elif action == self._j.A_START:
-            session_hash = transport_data['session_hash']
-            peer_candidates = transport_data['peer_candidates']
-            stream_object = content_data['stream_object']
+            session_hash = transport_data["session_hash"]
+            peer_candidates = transport_data["peer_candidates"]
+            stream_object = content_data["stream_object"]
             self._s5b.associateStreamObject(client, session_hash, stream_object)
-            stream_d = transport_data.pop('stream_d')
-            stream_d.chainDeferred(content_data['finished_d'])
-            peer_session_hash = transport_data['peer_session_hash']
-            d = self._s5b.getBestCandidate(client, peer_candidates, session_hash, peer_session_hash)
-            d.addCallback(self._foundPeerCandidate, session, transport_data, content_name, client)
+            stream_d = transport_data.pop("stream_d")
+            stream_d.chainDeferred(content_data["finished_d"])
+            peer_session_hash = transport_data["peer_session_hash"]
+            d = self._s5b.getBestCandidate(
+                client, peer_candidates, session_hash, peer_session_hash
+            )
+            d.addCallback(
+                self._foundPeerCandidate, session, transport_data, content_name, client
+            )
 
         elif action == self._j.A_SESSION_INITIATE:
             # responder side, we select a candidate in the ones sent by initiator
             # and we give our candidates
-            assert 'peer_candidates' not in transport_data
-            sid = transport_data['sid'] = transport_elt['sid']
-            session_hash = transport_data['session_hash'] = self._s5b.getSessionHash(client.jid, session['peer_jid'], sid)
-            peer_session_hash = transport_data['peer_session_hash'] = self._s5b.getSessionHash(session['peer_jid'], client.jid, sid) # requester and target are inversed for peer candidates
-            peer_candidates = transport_data['peer_candidates'] = self._parseCandidates(transport_elt)
-            stream_object = content_data['stream_object']
+            assert "peer_candidates" not in transport_data
+            sid = transport_data["sid"] = transport_elt["sid"]
+            session_hash = transport_data["session_hash"] = self._s5b.getSessionHash(
+                client.jid, session["peer_jid"], sid
+            )
+            peer_session_hash = transport_data[
+                "peer_session_hash"
+            ] = self._s5b.getSessionHash(
+                session["peer_jid"], client.jid, sid
+            )  # requester and target are inversed for peer candidates
+            peer_candidates = transport_data["peer_candidates"] = self._parseCandidates(
+                transport_elt
+            )
+            stream_object = content_data["stream_object"]
             stream_d = self._s5b.registerHash(client, session_hash, stream_object)
-            stream_d.chainDeferred(content_data['finished_d'])
-            d = self._s5b.getBestCandidate(client, peer_candidates, session_hash, peer_session_hash)
-            d.addCallback(self._foundPeerCandidate, session, transport_data, content_name, client)
+            stream_d.chainDeferred(content_data["finished_d"])
+            d = self._s5b.getBestCandidate(
+                client, peer_candidates, session_hash, peer_session_hash
+            )
+            d.addCallback(
+                self._foundPeerCandidate, session, transport_data, content_name, client
+            )
             candidates = yield self._s5b.getCandidates(client)
             # we remove duplicate candidates
-            candidates = [candidate for candidate in candidates if candidate not in peer_candidates]
+            candidates = [
+                candidate for candidate in candidates if candidate not in peer_candidates
+            ]
 
-            transport_data['candidates'] = candidates
+            transport_data["candidates"] = candidates
             # we can now build a new <transport> element with our candidates
-            transport_elt = self._buildCandidates(session, candidates, sid, session_hash, client)
+            transport_elt = self._buildCandidates(
+                session, candidates, sid, session_hash, client
+            )
 
         elif action == self._j.A_TRANSPORT_INFO:
             # transport-info can be about candidate or proxy activation
             candidate_elt = None
 
-            for method, names in ((self._candidateInfo, ('candidate-used', 'candidate-error')),
-                                  (self._proxyActivationInfo, ('activated', 'proxy-error'))):
+            for method, names in (
+                (self._candidateInfo, ("candidate-used", "candidate-error")),
+                (self._proxyActivationInfo, ("activated", "proxy-error")),
+            ):
                 for name in names:
                     try:
                         candidate_elt = transport_elt.elements(NS_JINGLE_S5B, name).next()
                     except StopIteration:
                         continue
                     else:
-                        method(candidate_elt, session, content_name, transport_data, client)
+                        method(
+                            candidate_elt, session, content_name, transport_data, client
+                        )
                         break
 
             if candidate_elt is None:
-                log.warning(u"Unexpected transport element: {}".format(transport_elt.toXml()))
+                log.warning(
+                    u"Unexpected transport element: {}".format(transport_elt.toXml())
+                )
         elif action == self._j.A_DESTROY:
             # the transport is replaced (fallback ?), We need mainly to kill XEP-0065 session.
             # note that sid argument is not necessary for sessions created by this plugin
-            self._s5b.killSession(None, transport_data['session_hash'], None, client)
+            self._s5b.killSession(None, transport_data["session_hash"], None, client)
         else:
             log.warning(u"FIXME: unmanaged action {}".format(action))
 
@@ -415,9 +492,9 @@
         if reason_elt.decline:
             log.debug(u"Session declined, deleting S5B session")
             # we just need to clean the S5B session if it is declined
-            content_data = session['contents'][content_name]
-            transport_data = content_data['transport_data']
-            self._s5b.killSession(None, transport_data['session_hash'], None, client)
+            content_data = session["contents"][content_name]
+            transport_data = content_data["transport_data"]
+            self._s5b.killSession(None, transport_data["session_hash"], None, client)
 
     def _doFallback(self, feature_checked, session, content_name, client):
         """Do the fallback, method called once feature is checked
@@ -425,10 +502,14 @@
          @param feature_checked(bool): True if other peer can do IBB
          """
         if not feature_checked:
-            log.warning(u"Other peer can't manage jingle IBB, be have to terminate the session")
+            log.warning(
+                u"Other peer can't manage jingle IBB, be have to terminate the session"
+            )
             self._j.terminate(client, self._j.REASON_CONNECTIVITY_ERROR, session)
         else:
-            self._j.transportReplace(client, self._jingle_ibb.NAMESPACE, session, content_name)
+            self._j.transportReplace(
+                client, self._jingle_ibb.NAMESPACE, session, content_name
+            )
 
     def doFallback(self, session, content_name, client):
         """Fallback to IBB transport, used in last resort
@@ -437,14 +518,18 @@
         @param content_name(unicode): name of the current content
         @param client(unicode): %(doc_client)s
         """
-        if session['role'] != self._j.ROLE_INITIATOR:
+        if session["role"] != self._j.ROLE_INITIATOR:
             # only initiator must do the fallback, see XEP-0260 §3
             return
         if self._jingle_ibb is None:
-            log.warning(u"Jingle IBB (XEP-0261) plugin is not available, we have to close the session")
+            log.warning(
+                u"Jingle IBB (XEP-0261) plugin is not available, we have to close the session"
+            )
             self._j.terminate(client, self._j.REASON_CONNECTIVITY_ERROR, session)
         else:
-            d = self.host.hasFeature(client, self._jingle_ibb.NAMESPACE, session['peer_jid'])
+            d = self.host.hasFeature(
+                client, self._jingle_ibb.NAMESPACE, session["peer_jid"]
+            )
             d.addCallback(self._doFallback, session, content_name, client)
         return d
 
@@ -452,8 +537,8 @@
 class XEP_0260_handler(XMPPHandler):
     implements(iwokkel.IDisco)
 
-    def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
+    def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
         return [disco.DiscoFeature(NS_JINGLE_S5B)]
 
-    def getDiscoItems(self, requestor, target, nodeIdentifier=''):
+    def getDiscoItems(self, requestor, target, nodeIdentifier=""):
         return []
--- a/sat/plugins/plugin_xep_0261.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_xep_0261.py	Wed Jun 27 20:14:46 2018 +0200
@@ -20,6 +20,7 @@
 from sat.core.i18n import _
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from wokkel import disco, iwokkel
 from zope.interface import implements
@@ -32,7 +33,7 @@
     from wokkel.subprotocols import XMPPHandler
 
 
-NS_JINGLE_IBB = 'urn:xmpp:jingle:transports:ibb:1'
+NS_JINGLE_IBB = "urn:xmpp:jingle:transports:ibb:1"
 
 PLUGIN_INFO = {
     C.PI_NAME: "Jingle In-Band Bytestreams",
@@ -43,52 +44,58 @@
     C.PI_DEPENDENCIES: ["XEP-0166", "XEP-0047"],
     C.PI_MAIN: "XEP_0261",
     C.PI_HANDLER: "yes",
-    C.PI_DESCRIPTION: _("""Implementation of Jingle In-Band Bytestreams""")
+    C.PI_DESCRIPTION: _("""Implementation of Jingle In-Band Bytestreams"""),
 }
 
 
 class XEP_0261(object):
-    NAMESPACE = NS_JINGLE_IBB # used by XEP-0260 plugin for transport-replace
+    NAMESPACE = NS_JINGLE_IBB  # used by XEP-0260 plugin for transport-replace
 
     def __init__(self, host):
         log.info(_("plugin Jingle In-Band Bytestreams"))
         self.host = host
-        self._j = host.plugins["XEP-0166"] # shortcut to access jingle
-        self._ibb = host.plugins["XEP-0047"] # and in-band bytestream
-        self._j.registerTransport(NS_JINGLE_IBB, self._j.TRANSPORT_STREAMING, self, -10000) # must be the lowest priority
+        self._j = host.plugins["XEP-0166"]  # shortcut to access jingle
+        self._ibb = host.plugins["XEP-0047"]  # and in-band bytestream
+        self._j.registerTransport(
+            NS_JINGLE_IBB, self._j.TRANSPORT_STREAMING, self, -10000
+        )  # must be the lowest priority
 
     def getHandler(self, client):
         return XEP_0261_handler()
 
     def jingleSessionInit(self, client, session, content_name):
         transport_elt = domish.Element((NS_JINGLE_IBB, "transport"))
-        content_data = session['contents'][content_name]
-        transport_data = content_data['transport_data']
-        transport_data['block_size'] = self._ibb.BLOCK_SIZE
-        transport_elt['block-size'] = unicode(transport_data['block_size'])
-        transport_elt['sid'] = transport_data['sid'] = unicode(uuid.uuid4())
+        content_data = session["contents"][content_name]
+        transport_data = content_data["transport_data"]
+        transport_data["block_size"] = self._ibb.BLOCK_SIZE
+        transport_elt["block-size"] = unicode(transport_data["block_size"])
+        transport_elt["sid"] = transport_data["sid"] = unicode(uuid.uuid4())
         return transport_elt
 
     def jingleHandler(self, client, action, session, content_name, transport_elt):
-        content_data = session['contents'][content_name]
-        transport_data = content_data['transport_data']
-        if action in (self._j.A_SESSION_ACCEPT,
-                      self._j.A_ACCEPTED_ACK,
-                      self._j.A_TRANSPORT_ACCEPT):
+        content_data = session["contents"][content_name]
+        transport_data = content_data["transport_data"]
+        if action in (
+            self._j.A_SESSION_ACCEPT,
+            self._j.A_ACCEPTED_ACK,
+            self._j.A_TRANSPORT_ACCEPT,
+        ):
             pass
         elif action in (self._j.A_SESSION_INITIATE, self._j.A_TRANSPORT_REPLACE):
-            transport_data['sid'] = transport_elt['sid']
+            transport_data["sid"] = transport_elt["sid"]
         elif action in (self._j.A_START, self._j.A_PREPARE_RESPONDER):
-            peer_jid = session['peer_jid']
-            sid = transport_data['sid']
-            stream_object = content_data['stream_object']
+            peer_jid = session["peer_jid"]
+            sid = transport_data["sid"]
+            stream_object = content_data["stream_object"]
             if action == self._j.A_START:
-                block_size = transport_data['block_size']
-                d = self._ibb.startStream(client, stream_object, peer_jid, sid, block_size)
-                d.chainDeferred(content_data['finished_d'])
+                block_size = transport_data["block_size"]
+                d = self._ibb.startStream(
+                    client, stream_object, peer_jid, sid, block_size
+                )
+                d.chainDeferred(content_data["finished_d"])
             else:
                 d = self._ibb.createSession(client, stream_object, peer_jid, sid)
-                d.chainDeferred(content_data['finished_d'])
+                d.chainDeferred(content_data["finished_d"])
         else:
             log.warning(u"FIXME: unmanaged action {}".format(action))
         return transport_elt
@@ -97,8 +104,8 @@
 class XEP_0261_handler(XMPPHandler):
     implements(iwokkel.IDisco)
 
-    def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
+    def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
         return [disco.DiscoFeature(NS_JINGLE_IBB)]
 
-    def getDiscoItems(self, requestor, target, nodeIdentifier=''):
+    def getDiscoItems(self, requestor, target, nodeIdentifier=""):
         return []
--- a/sat/plugins/plugin_xep_0264.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_xep_0264.py	Wed Jun 27 20:14:46 2018 +0200
@@ -21,6 +21,7 @@
 from sat.core.i18n import _
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from twisted.internet import threads
 from twisted.python.failure import Failure
@@ -31,13 +32,17 @@
 
 from sat.core import exceptions
 import hashlib
+
 try:
     from PIL import Image
 except:
-    raise exceptions.MissingModule(u"Missing module pillow, please download/install it from https://python-pillow.github.io")
+    raise exceptions.MissingModule(
+        u"Missing module pillow, please download/install it from https://python-pillow.github.io"
+    )
 
-# cf. https://stackoverflow.com/a/23575424
+#  cf. https://stackoverflow.com/a/23575424
 from PIL import ImageFile
+
 ImageFile.LOAD_TRUNCATED_IMAGES = True
 
 try:
@@ -46,10 +51,10 @@
     from wokkel.subprotocols import XMPPHandler
 
 
-MIME_TYPE = u'image/jpeg'
-SAVE_FORMAT = u'JPEG' # (cf. Pillow documentation)
+MIME_TYPE = u"image/jpeg"
+SAVE_FORMAT = u"JPEG"  # (cf. Pillow documentation)
 
-NS_THUMBS = 'urn:xmpp:thumbs:1'
+NS_THUMBS = "urn:xmpp:thumbs:1"
 
 PLUGIN_INFO = {
     C.PI_NAME: "XEP-0264",
@@ -60,7 +65,7 @@
     C.PI_DEPENDENCIES: ["XEP-0234"],
     C.PI_MAIN: "XEP_0264",
     C.PI_HANDLER: "yes",
-    C.PI_DESCRIPTION: _("""Thumbnails handling""")
+    C.PI_DESCRIPTION: _("""Thumbnails handling"""),
 }
 
 
@@ -81,39 +86,39 @@
 
     def _addFileThumbnails(self, file_elt, extra_args):
         try:
-            thumbnails = extra_args[u'extra'][C.KEY_THUMBNAILS]
+            thumbnails = extra_args[u"extra"][C.KEY_THUMBNAILS]
         except KeyError:
             return
         for thumbnail in thumbnails:
-            thumbnail_elt = file_elt.addElement((NS_THUMBS, u'thumbnail'))
-            thumbnail_elt['uri'] = u'cid:' + thumbnail['id']
-            thumbnail_elt['media-type'] = MIME_TYPE
-            width, height = thumbnail['size']
-            thumbnail_elt['width'] = unicode(width)
-            thumbnail_elt['height'] = unicode(height)
+            thumbnail_elt = file_elt.addElement((NS_THUMBS, u"thumbnail"))
+            thumbnail_elt["uri"] = u"cid:" + thumbnail["id"]
+            thumbnail_elt["media-type"] = MIME_TYPE
+            width, height = thumbnail["size"]
+            thumbnail_elt["width"] = unicode(width)
+            thumbnail_elt["height"] = unicode(height)
         return True
 
     def _getFileThumbnails(self, file_elt, file_data):
         thumbnails = []
-        for thumbnail_elt in file_elt.elements(NS_THUMBS, u'thumbnail'):
-            uri = thumbnail_elt['uri']
-            if uri.startswith ('cid:'):
-                thumbnail = {'id': uri[4:]}
-            width = thumbnail_elt.getAttribute('width')
-            height = thumbnail_elt.getAttribute('height')
+        for thumbnail_elt in file_elt.elements(NS_THUMBS, u"thumbnail"):
+            uri = thumbnail_elt["uri"]
+            if uri.startswith("cid:"):
+                thumbnail = {"id": uri[4:]}
+            width = thumbnail_elt.getAttribute("width")
+            height = thumbnail_elt.getAttribute("height")
             if width and height:
                 try:
-                    thumbnail['size'] = int(width), int(height)
+                    thumbnail["size"] = int(width), int(height)
                 except ValueError:
                     pass
             try:
-                thumbnail['mime_type'] = thumbnail_elt['media-type']
+                thumbnail["mime_type"] = thumbnail_elt["media-type"]
             except KeyError:
                 pass
             thumbnails.append(thumbnail)
 
         if thumbnails:
-            file_data.setdefault('extra', {})[C.KEY_THUMBNAILS] = thumbnails
+            file_data.setdefault("extra", {})[C.KEY_THUMBNAILS] = thumbnails
         return True
 
     ## thumbnails generation ##
@@ -145,11 +150,8 @@
         uid = self.getThumbId(image_uid or source_path, size)
 
         with self.host.common_cache.cacheData(
-            PLUGIN_INFO[C.PI_IMPORT_NAME],
-            uid,
-            MIME_TYPE,
-            max_age,
-            ) as f:
+            PLUGIN_INFO[C.PI_IMPORT_NAME], uid, MIME_TYPE, max_age
+        ) as f:
             img.save(f, SAVE_FORMAT)
 
         return img.size, uid
@@ -171,17 +173,19 @@
         """
 
         d = threads.deferToThread(
-                self._blockingGenThumb, source_path, size, max_age, image_uid=image_uid)
-        d.addErrback(lambda failure_:
-            log.error(u"thumbnail generation error: {}".format(failure_)))
+            self._blockingGenThumb, source_path, size, max_age, image_uid=image_uid
+        )
+        d.addErrback(
+            lambda failure_: log.error(u"thumbnail generation error: {}".format(failure_))
+        )
         return d
 
 
 class XEP_0264_handler(XMPPHandler):
     implements(iwokkel.IDisco)
 
-    def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
+    def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
         return [disco.DiscoFeature(NS_THUMBS)]
 
-    def getDiscoItems(self, requestor, target, nodeIdentifier=''):
+    def getDiscoItems(self, requestor, target, nodeIdentifier=""):
         return []
--- a/sat/plugins/plugin_xep_0277.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_xep_0277.py	Wed Jun 27 20:14:46 2018 +0200
@@ -20,6 +20,7 @@
 from sat.core.i18n import _
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from twisted.words.protocols.jabber import jid, error
 from twisted.words.protocols.jabber.xmlstream import XMPPHandler
@@ -43,10 +44,10 @@
 import calendar
 import urlparse
 
-NS_MICROBLOG = 'urn:xmpp:microblog:0'
-NS_ATOM = 'http://www.w3.org/2005/Atom'
+NS_MICROBLOG = "urn:xmpp:microblog:0"
+NS_ATOM = "http://www.w3.org/2005/Atom"
 NS_PUBSUB_EVENT = "{}{}".format(pubsub.NS_PUBSUB, "#event")
-NS_COMMENT_PREFIX = '{}:comments/'.format(NS_MICROBLOG)
+NS_COMMENT_PREFIX = "{}:comments/".format(NS_MICROBLOG)
 
 
 PLUGIN_INFO = {
@@ -58,7 +59,7 @@
     C.PI_RECOMMENDATIONS: ["XEP-0059", "EXTRA-PEP"],
     C.PI_MAIN: "XEP_0277",
     C.PI_HANDLER: "yes",
-    C.PI_DESCRIPTION: _("""Implementation of microblogging Protocol""")
+    C.PI_DESCRIPTION: _("""Implementation of microblogging Protocol"""),
 }
 
 
@@ -72,49 +73,97 @@
     def __init__(self, host):
         log.info(_(u"Microblogging plugin initialization"))
         self.host = host
-        host.registerNamespace('microblog', NS_MICROBLOG)
-        self._p = self.host.plugins["XEP-0060"] # this facilitate the access to pubsub plugin
+        host.registerNamespace("microblog", NS_MICROBLOG)
+        self._p = self.host.plugins[
+            "XEP-0060"
+        ]  # this facilitate the access to pubsub plugin
         self.rt_sessions = sat_defer.RTDeferredSessions()
-        self.host.plugins["XEP-0060"].addManagedNode(NS_MICROBLOG, items_cb=self._itemsReceived)
+        self.host.plugins["XEP-0060"].addManagedNode(
+            NS_MICROBLOG, items_cb=self._itemsReceived
+        )
 
-        host.bridge.addMethod("mbSend", ".plugin",
-                              in_sign='ssa{ss}s', out_sign='',
-                              method=self._mbSend,
-                              async=True)
-        host.bridge.addMethod("mbRetract", ".plugin",
-                              in_sign='ssss', out_sign='',
-                              method=self._mbRetract,
-                              async=True)
-        host.bridge.addMethod("mbGet", ".plugin",
-                              in_sign='ssiasa{ss}s', out_sign='(aa{ss}a{ss})',
-                              method=self._mbGet,
-                              async=True)
-        host.bridge.addMethod("mbSetAccess", ".plugin", in_sign='ss', out_sign='',
-                              method=self.mbSetAccess,
-                              async=True)
-        host.bridge.addMethod("mbSubscribeToMany", ".plugin", in_sign='sass', out_sign='s',
-                              method=self._mbSubscribeToMany)
-        host.bridge.addMethod("mbGetFromManyRTResult", ".plugin", in_sign='ss', out_sign='(ua(sssaa{ss}a{ss}))',
-                              method=self._mbGetFromManyRTResult, async=True)
-        host.bridge.addMethod("mbGetFromMany", ".plugin", in_sign='sasia{ss}s', out_sign='s',
-                              method=self._mbGetFromMany)
-        host.bridge.addMethod("mbGetFromManyWithCommentsRTResult", ".plugin", in_sign='ss', out_sign='(ua(sssa(a{ss}a(sssaa{ss}a{ss}))a{ss}))',
-                              method=self._mbGetFromManyWithCommentsRTResult, async=True)
-        host.bridge.addMethod("mbGetFromManyWithComments", ".plugin", in_sign='sasiia{ss}a{ss}s', out_sign='s',
-                              method=self._mbGetFromManyWithComments)
+        host.bridge.addMethod(
+            "mbSend",
+            ".plugin",
+            in_sign="ssa{ss}s",
+            out_sign="",
+            method=self._mbSend,
+            async=True,
+        )
+        host.bridge.addMethod(
+            "mbRetract",
+            ".plugin",
+            in_sign="ssss",
+            out_sign="",
+            method=self._mbRetract,
+            async=True,
+        )
+        host.bridge.addMethod(
+            "mbGet",
+            ".plugin",
+            in_sign="ssiasa{ss}s",
+            out_sign="(aa{ss}a{ss})",
+            method=self._mbGet,
+            async=True,
+        )
+        host.bridge.addMethod(
+            "mbSetAccess",
+            ".plugin",
+            in_sign="ss",
+            out_sign="",
+            method=self.mbSetAccess,
+            async=True,
+        )
+        host.bridge.addMethod(
+            "mbSubscribeToMany",
+            ".plugin",
+            in_sign="sass",
+            out_sign="s",
+            method=self._mbSubscribeToMany,
+        )
+        host.bridge.addMethod(
+            "mbGetFromManyRTResult",
+            ".plugin",
+            in_sign="ss",
+            out_sign="(ua(sssaa{ss}a{ss}))",
+            method=self._mbGetFromManyRTResult,
+            async=True,
+        )
+        host.bridge.addMethod(
+            "mbGetFromMany",
+            ".plugin",
+            in_sign="sasia{ss}s",
+            out_sign="s",
+            method=self._mbGetFromMany,
+        )
+        host.bridge.addMethod(
+            "mbGetFromManyWithCommentsRTResult",
+            ".plugin",
+            in_sign="ss",
+            out_sign="(ua(sssa(a{ss}a(sssaa{ss}a{ss}))a{ss}))",
+            method=self._mbGetFromManyWithCommentsRTResult,
+            async=True,
+        )
+        host.bridge.addMethod(
+            "mbGetFromManyWithComments",
+            ".plugin",
+            in_sign="sasiia{ss}a{ss}s",
+            out_sign="s",
+            method=self._mbGetFromManyWithComments,
+        )
 
     def getHandler(self, client):
         return XEP_0277_handler()
 
     def _checkFeaturesCb(self, available):
-        return {'available': C.BOOL_TRUE}
+        return {"available": C.BOOL_TRUE}
 
     def _checkFeaturesEb(self, fail):
-        return {'available': C.BOOL_FALSE}
+        return {"available": C.BOOL_FALSE}
 
     def getFeatures(self, profile):
         client = self.host.getClient(profile)
-        d = self.host.checkFeatures(client, [], identity=('pubsub', 'pep'))
+        d = self.host.checkFeatures(client, [], identity=("pubsub", "pep"))
         d.addCallbacks(self._checkFeaturesCb, self._checkFeaturesEb)
         return d
 
@@ -122,18 +171,27 @@
 
     def _itemsReceived(self, client, itemsEvent):
         """Callback which manage items notifications (publish + retract)"""
+
         def manageItem(data, event):
-            self.host.bridge.psEvent(C.PS_MICROBLOG, itemsEvent.sender.full(), itemsEvent.nodeIdentifier, event, data, client.profile)
+            self.host.bridge.psEvent(
+                C.PS_MICROBLOG,
+                itemsEvent.sender.full(),
+                itemsEvent.nodeIdentifier,
+                event,
+                data,
+                client.profile,
+            )
 
         for item in itemsEvent.items:
             if item.name == C.PS_ITEM:
-                self.item2mbdata(item).addCallbacks(manageItem, lambda failure: None, (C.PS_PUBLISH,))
+                self.item2mbdata(item).addCallbacks(
+                    manageItem, lambda failure: None, (C.PS_PUBLISH,)
+                )
             elif item.name == C.PS_RETRACT:
-                manageItem({'id': item['id']}, C.PS_RETRACT)
+                manageItem({"id": item["id"]}, C.PS_RETRACT)
             else:
                 raise exceptions.InternalError("Invalid event value")
 
-
     ## data/item transformation ##
 
     @defer.inlineCallbacks
@@ -156,13 +214,17 @@
             """
             if key in microblog_data:
                 if not increment:
-                    raise failure.Failure(exceptions.DataError("key {} is already present for item {}").format(key, item_elt['id']))
+                    raise failure.Failure(
+                        exceptions.DataError(
+                            "key {} is already present for item {}"
+                        ).format(key, item_elt["id"])
+                    )
                 else:
-                    idx=1 # the idx 0 is the key without suffix
+                    idx = 1  # the idx 0 is the key without suffix
                     fmt = "{}#{}"
                     new_key = fmt.format(key, idx)
                     while new_key in microblog_data:
-                        idx+=1
+                        idx += 1
                         new_key = fmt.format(key, idx)
                     key = new_key
             return key
@@ -170,49 +232,62 @@
         @defer.inlineCallbacks
         def parseElement(elem):
             """Parse title/content elements and fill microblog_data accordingly"""
-            type_ = elem.getAttribute('type')
-            if type_ == 'xhtml':
+            type_ = elem.getAttribute("type")
+            if type_ == "xhtml":
                 data_elt = elem.firstChildElement()
                 if data_elt is None:
-                    raise failure.Failure(exceptions.DataError(u"XHML content not wrapped in a <div/> element, this is not standard !"))
+                    raise failure.Failure(
+                        exceptions.DataError(
+                            u"XHML content not wrapped in a <div/> element, this is not standard !"
+                        )
+                    )
                 if data_elt.uri != C.NS_XHTML:
-                    raise failure.Failure(exceptions.DataError(_('Content of type XHTML must declare its namespace!')))
-                key = check_conflict(u'{}_xhtml'.format(elem.name))
+                    raise failure.Failure(
+                        exceptions.DataError(
+                            _("Content of type XHTML must declare its namespace!")
+                        )
+                    )
+                key = check_conflict(u"{}_xhtml".format(elem.name))
                 data = data_elt.toXml()
-                microblog_data[key] = yield self.host.plugins["TEXT-SYNTAXES"].cleanXHTML(data)
+                microblog_data[key] = yield self.host.plugins["TEXT-SYNTAXES"].cleanXHTML(
+                    data
+                )
             else:
                 key = check_conflict(elem.name)
                 microblog_data[key] = unicode(elem)
 
-
-        id_ = item_elt.getAttribute('id', '') # there can be no id for transient nodes
-        microblog_data[u'id'] = id_
+        id_ = item_elt.getAttribute("id", "")  # there can be no id for transient nodes
+        microblog_data[u"id"] = id_
         if item_elt.uri not in (pubsub.NS_PUBSUB, NS_PUBSUB_EVENT):
-            msg = u"Unsupported namespace {ns} in pubsub item {id_}".format(ns=item_elt.uri, id_=id_)
+            msg = u"Unsupported namespace {ns} in pubsub item {id_}".format(
+                ns=item_elt.uri, id_=id_
+            )
             log.warning(msg)
             raise failure.Failure(exceptions.DataError(msg))
 
         try:
-            entry_elt = item_elt.elements(NS_ATOM, 'entry').next()
+            entry_elt = item_elt.elements(NS_ATOM, "entry").next()
         except StopIteration:
-            msg = u'No atom entry found in the pubsub item {}'.format(id_)
+            msg = u"No atom entry found in the pubsub item {}".format(id_)
             raise failure.Failure(exceptions.DataError(msg))
 
         # language
         try:
-            microblog_data[u'language'] = entry_elt[(C.NS_XML, u'lang')].strip()
+            microblog_data[u"language"] = entry_elt[(C.NS_XML, u"lang")].strip()
         except KeyError:
             pass
 
         # atom:id
         try:
-            id_elt = entry_elt.elements(NS_ATOM, 'id').next()
+            id_elt = entry_elt.elements(NS_ATOM, "id").next()
         except StopIteration:
-            msg = u'No atom id found in the pubsub item {}, this is not standard !'.format(id_)
+            msg = u"No atom id found in the pubsub item {}, this is not standard !".format(
+                id_
+            )
             log.warning(msg)
-            microblog_data[u'atom_id'] = ""
+            microblog_data[u"atom_id"] = ""
         else:
-            microblog_data[u'atom_id'] = unicode(id_elt)
+            microblog_data[u"atom_id"] = unicode(id_elt)
 
         # title/content(s)
 
@@ -225,9 +300,9 @@
         # except StopIteration:
         #     msg = u'No atom title found in the pubsub item {}'.format(id_)
         #     raise failure.Failure(exceptions.DataError(msg))
-        title_elts = list(entry_elt.elements(NS_ATOM, 'title'))
+        title_elts = list(entry_elt.elements(NS_ATOM, "title"))
         if not title_elts:
-            msg = u'No atom title found in the pubsub item {}'.format(id_)
+            msg = u"No atom title found in the pubsub item {}".format(id_)
             raise failure.Failure(exceptions.DataError(msg))
         for title_elt in title_elts:
             yield parseElement(title_elt)
@@ -235,113 +310,146 @@
         # FIXME: as for <title/>, Atom only authorise at most 1 content
         #        but XEP-0277 allows several ones. So for no we handle as
         #        if more than one can be present
-        for content_elt in entry_elt.elements(NS_ATOM, 'content'):
+        for content_elt in entry_elt.elements(NS_ATOM, "content"):
             yield parseElement(content_elt)
 
         # we check that text content is present
-        for key in ('title', 'content'):
-            if key not in microblog_data and ('{}_xhtml'.format(key)) in microblog_data:
-                log.warning(u"item {id_} provide a {key}_xhtml data but not a text one".format(id_=id_, key=key))
+        for key in ("title", "content"):
+            if key not in microblog_data and ("{}_xhtml".format(key)) in microblog_data:
+                log.warning(
+                    u"item {id_} provide a {key}_xhtml data but not a text one".format(
+                        id_=id_, key=key
+                    )
+                )
                 # ... and do the conversion if it's not
-                microblog_data[key] = yield self.host.plugins["TEXT-SYNTAXES"].\
-                                            convert(microblog_data[u'{}_xhtml'.format(key)],
-                                            self.host.plugins["TEXT-SYNTAXES"].SYNTAX_XHTML,
-                                            self.host.plugins["TEXT-SYNTAXES"].SYNTAX_TEXT,
-                                            False)
+                microblog_data[key] = yield self.host.plugins["TEXT-SYNTAXES"].convert(
+                    microblog_data[u"{}_xhtml".format(key)],
+                    self.host.plugins["TEXT-SYNTAXES"].SYNTAX_XHTML,
+                    self.host.plugins["TEXT-SYNTAXES"].SYNTAX_TEXT,
+                    False,
+                )
 
-        if 'content' not in microblog_data:
+        if "content" not in microblog_data:
             # use the atom title data as the microblog body content
-            microblog_data[u'content'] = microblog_data[u'title']
-            del microblog_data[u'title']
-            if 'title_xhtml' in microblog_data:
-                microblog_data[u'content_xhtml'] = microblog_data[u'title_xhtml']
-                del microblog_data[u'title_xhtml']
+            microblog_data[u"content"] = microblog_data[u"title"]
+            del microblog_data[u"title"]
+            if "title_xhtml" in microblog_data:
+                microblog_data[u"content_xhtml"] = microblog_data[u"title_xhtml"]
+                del microblog_data[u"title_xhtml"]
 
         # published/updated dates
         try:
-            updated_elt = entry_elt.elements(NS_ATOM, 'updated').next()
+            updated_elt = entry_elt.elements(NS_ATOM, "updated").next()
         except StopIteration:
-            msg = u'No atom updated element found in the pubsub item {}'.format(id_)
+            msg = u"No atom updated element found in the pubsub item {}".format(id_)
             raise failure.Failure(exceptions.DataError(msg))
-        microblog_data[u'updated'] = unicode(calendar.timegm(dateutil.parser.parse(unicode(updated_elt)).utctimetuple()))
+        microblog_data[u"updated"] = unicode(
+            calendar.timegm(dateutil.parser.parse(unicode(updated_elt)).utctimetuple())
+        )
         try:
-            published_elt = entry_elt.elements(NS_ATOM, 'published').next()
+            published_elt = entry_elt.elements(NS_ATOM, "published").next()
         except StopIteration:
-            microblog_data[u'published'] = microblog_data[u'updated']
+            microblog_data[u"published"] = microblog_data[u"updated"]
         else:
-            microblog_data[u'published'] = unicode(calendar.timegm(dateutil.parser.parse(unicode(published_elt)).utctimetuple()))
+            microblog_data[u"published"] = unicode(
+                calendar.timegm(
+                    dateutil.parser.parse(unicode(published_elt)).utctimetuple()
+                )
+            )
 
         # links
-        for link_elt in entry_elt.elements(NS_ATOM, 'link'):
-            if link_elt.getAttribute('rel') == 'replies' and link_elt.getAttribute('title') == 'comments':
-                key = check_conflict('comments', True)
-                microblog_data[key] = link_elt['href']
+        for link_elt in entry_elt.elements(NS_ATOM, "link"):
+            if (
+                link_elt.getAttribute("rel") == "replies"
+                and link_elt.getAttribute("title") == "comments"
+            ):
+                key = check_conflict("comments", True)
+                microblog_data[key] = link_elt["href"]
                 try:
                     service, node = self.parseCommentUrl(microblog_data[key])
                 except:
                     log.warning(u"Can't parse url {}".format(microblog_data[key]))
                     del microblog_data[key]
                 else:
-                    microblog_data[u'{}_service'.format(key)] = service.full()
-                    microblog_data[u'{}_node'.format(key)] = node
+                    microblog_data[u"{}_service".format(key)] = service.full()
+                    microblog_data[u"{}_node".format(key)] = node
             else:
-                rel = link_elt.getAttribute('rel','')
-                title = link_elt.getAttribute('title','')
-                href = link_elt.getAttribute('href','')
-                log.warning(u"Unmanaged link element: rel={rel} title={title} href={href}".format(rel=rel, title=title, href=href))
+                rel = link_elt.getAttribute("rel", "")
+                title = link_elt.getAttribute("title", "")
+                href = link_elt.getAttribute("href", "")
+                log.warning(
+                    u"Unmanaged link element: rel={rel} title={title} href={href}".format(
+                        rel=rel, title=title, href=href
+                    )
+                )
 
         # author
         try:
-            author_elt = entry_elt.elements(NS_ATOM, 'author').next()
+            author_elt = entry_elt.elements(NS_ATOM, "author").next()
         except StopIteration:
             log.debug(u"Can't find author element in item {}".format(id_))
         else:
             publisher = item_elt.getAttribute("publisher")
             # name
             try:
-                name_elt = author_elt.elements(NS_ATOM, 'name').next()
+                name_elt = author_elt.elements(NS_ATOM, "name").next()
             except StopIteration:
-                log.warning(u"No name element found in author element of item {}".format(id_))
+                log.warning(
+                    u"No name element found in author element of item {}".format(id_)
+                )
             else:
-                microblog_data[u'author'] = unicode(name_elt)
+                microblog_data[u"author"] = unicode(name_elt)
             # uri
             try:
-                uri_elt = author_elt.elements(NS_ATOM, 'uri').next()
+                uri_elt = author_elt.elements(NS_ATOM, "uri").next()
             except StopIteration:
-                log.debug(u"No uri element found in author element of item {}".format(id_))
+                log.debug(
+                    u"No uri element found in author element of item {}".format(id_)
+                )
                 if publisher:
-                    microblog_data[u'author_jid'] = publisher
+                    microblog_data[u"author_jid"] = publisher
             else:
                 uri = unicode(uri_elt)
                 if uri.startswith("xmpp:"):
                     uri = uri[5:]
-                    microblog_data[u'author_jid'] = uri
+                    microblog_data[u"author_jid"] = uri
                 else:
-                    microblog_data[u'author_jid'] = item_elt.getAttribute(u"publisher") or ""
+                    microblog_data[u"author_jid"] = (
+                        item_elt.getAttribute(u"publisher") or ""
+                    )
 
                 if not publisher:
                     log.debug(u"No publisher attribute, we can't verify author jid")
-                    microblog_data[u'author_jid_verified'] = C.BOOL_FALSE
+                    microblog_data[u"author_jid_verified"] = C.BOOL_FALSE
                 elif jid.JID(publisher).userhostJID() == jid.JID(uri).userhostJID():
-                    microblog_data[u'author_jid_verified'] = C.BOOL_TRUE
+                    microblog_data[u"author_jid_verified"] = C.BOOL_TRUE
                 else:
-                    log.warning(u"item atom:uri differ from publisher attribute, spoofing attempt ? atom:uri = {} publisher = {}".format(uri, item_elt.getAttribute("publisher")))
-                    microblog_data[u'author_jid_verified'] = C.BOOL_FALSE
+                    log.warning(
+                        u"item atom:uri differ from publisher attribute, spoofing attempt ? atom:uri = {} publisher = {}".format(
+                            uri, item_elt.getAttribute("publisher")
+                        )
+                    )
+                    microblog_data[u"author_jid_verified"] = C.BOOL_FALSE
             # email
             try:
-                email_elt = author_elt.elements(NS_ATOM, 'email').next()
+                email_elt = author_elt.elements(NS_ATOM, "email").next()
             except StopIteration:
                 pass
             else:
-                microblog_data[u'author_email'] = unicode(email_elt)
+                microblog_data[u"author_email"] = unicode(email_elt)
 
             # categories
-            categories = (category_elt.getAttribute('term','') for category_elt in entry_elt.elements(NS_ATOM, 'category'))
-            data_format.iter2dict('tag', categories, microblog_data)
+            categories = (
+                category_elt.getAttribute("term", "")
+                for category_elt in entry_elt.elements(NS_ATOM, "category")
+            )
+            data_format.iter2dict("tag", categories, microblog_data)
 
         ## the trigger ##
         # if other plugins have things to add or change
-        yield self.host.trigger.point("XEP-0277_item2data", item_elt, entry_elt, microblog_data)
+        yield self.host.trigger.point(
+            "XEP-0277_item2data", item_elt, entry_elt, microblog_data
+        )
 
         defer.returnValue(microblog_data)
 
@@ -357,115 +465,144 @@
             Needed to construct Atom id
         @return: deferred which fire domish.Element
         """
-        entry_elt = domish.Element((NS_ATOM, 'entry'))
+        entry_elt = domish.Element((NS_ATOM, "entry"))
 
         ## language ##
-        if u'language' in data:
-            entry_elt[(C.NS_XML, u'lang')] = data[u'language'].strip()
+        if u"language" in data:
+            entry_elt[(C.NS_XML, u"lang")] = data[u"language"].strip()
 
         ## content and title ##
         synt = self.host.plugins["TEXT-SYNTAXES"]
 
-        for elem_name in ('title', 'content'):
-            for type_ in ['', '_rich', '_xhtml']:
+        for elem_name in ("title", "content"):
+            for type_ in ["", "_rich", "_xhtml"]:
                 attr = "{}{}".format(elem_name, type_)
                 if attr in data:
                     elem = entry_elt.addElement(elem_name)
                     if type_:
-                        if type_ == '_rich':  # convert input from current syntax to XHTML
-                            xml_content = yield synt.convert(data[attr], synt.getCurrentSyntax(client.profile), "XHTML")
-                            if '{}_xhtml'.format(elem_name) in data:
-                                raise failure.Failure(exceptions.DataError(_("Can't have xhtml and rich content at the same time")))
+                        if type_ == "_rich":  # convert input from current syntax to XHTML
+                            xml_content = yield synt.convert(
+                                data[attr], synt.getCurrentSyntax(client.profile), "XHTML"
+                            )
+                            if "{}_xhtml".format(elem_name) in data:
+                                raise failure.Failure(
+                                    exceptions.DataError(
+                                        _(
+                                            "Can't have xhtml and rich content at the same time"
+                                        )
+                                    )
+                                )
                         else:
                             xml_content = data[attr]
 
-                        div_elt = xml_tools.ElementParser()(xml_content, namespace=C.NS_XHTML)
-                        if div_elt.name != 'div' or div_elt.uri != C.NS_XHTML or div_elt.attributes:
+                        div_elt = xml_tools.ElementParser()(
+                            xml_content, namespace=C.NS_XHTML
+                        )
+                        if (
+                            div_elt.name != "div"
+                            or div_elt.uri != C.NS_XHTML
+                            or div_elt.attributes
+                        ):
                             # we need a wrapping <div/> at the top with XHTML namespace
-                            wrap_div_elt = domish.Element((C.NS_XHTML, 'div'))
+                            wrap_div_elt = domish.Element((C.NS_XHTML, "div"))
                             wrap_div_elt.addChild(div_elt)
                             div_elt = wrap_div_elt
                         elem.addChild(div_elt)
-                        elem['type'] = 'xhtml'
+                        elem["type"] = "xhtml"
                         if elem_name not in data:
                             # there is raw text content, which is mandatory
                             # so we create one from xhtml content
                             elem_txt = entry_elt.addElement(elem_name)
-                            text_content = yield self.host.plugins["TEXT-SYNTAXES"].convert(xml_content,
+                            text_content = yield self.host.plugins[
+                                "TEXT-SYNTAXES"
+                            ].convert(
+                                xml_content,
                                 self.host.plugins["TEXT-SYNTAXES"].SYNTAX_XHTML,
                                 self.host.plugins["TEXT-SYNTAXES"].SYNTAX_TEXT,
-                                False)
+                                False,
+                            )
                             elem_txt.addContent(text_content)
-                            elem_txt['type'] = 'text'
+                            elem_txt["type"] = "text"
 
                     else:  # raw text only needs to be escaped to get HTML-safe sequence
                         elem.addContent(data[attr])
-                        elem['type'] = 'text'
+                        elem["type"] = "text"
 
         try:
-            entry_elt.elements(NS_ATOM, 'title').next()
+            entry_elt.elements(NS_ATOM, "title").next()
         except StopIteration:
             # we have no title element which is mandatory
             # so we transform content element to title
-            elems = list(entry_elt.elements(NS_ATOM, 'content'))
+            elems = list(entry_elt.elements(NS_ATOM, "content"))
             if not elems:
-                raise exceptions.DataError("There must be at least one content or title element")
+                raise exceptions.DataError(
+                    "There must be at least one content or title element"
+                )
             for elem in elems:
-                elem.name = 'title'
+                elem.name = "title"
 
         ## author ##
-        author_elt = entry_elt.addElement('author')
+        author_elt = entry_elt.addElement("author")
         try:
-            author_name = data['author']
+            author_name = data["author"]
         except KeyError:
             # FIXME: must use better name
             author_name = client.jid.user
-        author_elt.addElement('name', content=author_name)
+        author_elt.addElement("name", content=author_name)
 
         try:
-            author_jid_s = data['author_jid']
+            author_jid_s = data["author_jid"]
         except KeyError:
             author_jid_s = client.jid.userhost()
-        author_elt.addElement('uri', content="xmpp:{}".format(author_jid_s))
+        author_elt.addElement("uri", content="xmpp:{}".format(author_jid_s))
 
         try:
-            author_jid_s = data['author_email']
+            author_jid_s = data["author_email"]
         except KeyError:
             pass
 
         ## published/updated time ##
         current_time = time.time()
-        entry_elt.addElement('updated',
-            content = utils.xmpp_date(float(data.get('updated', current_time))))
-        entry_elt.addElement('published',
-            content = utils.xmpp_date(float(data.get('published', current_time))))
+        entry_elt.addElement(
+            "updated", content=utils.xmpp_date(float(data.get("updated", current_time)))
+        )
+        entry_elt.addElement(
+            "published",
+            content=utils.xmpp_date(float(data.get("published", current_time))),
+        )
 
         ## categories ##
         for tag in data_format.dict2iter("tag", data):
             category_elt = entry_elt.addElement("category")
-            category_elt['term'] = tag
+            category_elt["term"] = tag
 
         ## id ##
-        entry_id = data.get('id', xmpp_uri.buildXMPPUri(
-            u'pubsub',
-            path=service.full() if service is not None else client.jid.userhost(),
-            node=node,
-            item=item_id))
-        entry_elt.addElement('id', content=entry_id) #
+        entry_id = data.get(
+            "id",
+            xmpp_uri.buildXMPPUri(
+                u"pubsub",
+                path=service.full() if service is not None else client.jid.userhost(),
+                node=node,
+                item=item_id,
+            ),
+        )
+        entry_elt.addElement("id", content=entry_id)  #
 
         ## comments ##
-        if 'comments' in data:
-            link_elt = entry_elt.addElement('link')
-            link_elt['href'] = data['comments']
-            link_elt['rel'] = 'replies'
-            link_elt['title'] = 'comments'
+        if "comments" in data:
+            link_elt = entry_elt.addElement("link")
+            link_elt["href"] = data["comments"]
+            link_elt["rel"] = "replies"
+            link_elt["title"] = "comments"
 
         ## final item building ##
         item_elt = pubsub.Item(id=item_id, payload=entry_elt)
 
         ## the trigger ##
         # if other plugins have things to add or change
-        yield self.host.trigger.point("XEP-0277_data2entry", client, data, entry_elt, item_elt)
+        yield self.host.trigger.point(
+            "XEP-0277_data2entry", client, data, entry_elt, item_elt
+        )
 
         defer.returnValue(item_elt)
 
@@ -489,17 +626,21 @@
             if parent_service.user:
                 # we are on a PEP
                 if parent_service.host == client.jid.host:
-                    # it's our server, we use already found client.pubsub_service below
+                    #  it's our server, we use already found client.pubsub_service below
                     pass
                 else:
                     # other server, let's try to find a non PEP service there
-                    d = self.host.findServiceEntity(client, "pubsub", "service", parent_service)
+                    d = self.host.findServiceEntity(
+                        client, "pubsub", "service", parent_service
+                    )
                     d.addCallback(lambda entity: entity or parent_service)
             else:
                 # parent is already on a normal Pubsub service, we re-use it
                 return defer.succeed(parent_service)
 
-        return defer.succeed(client.pubsub_service if client.pubsub_service is not None else parent_service)
+        return defer.succeed(
+            client.pubsub_service if client.pubsub_service is not None else parent_service
+        )
 
     @defer.inlineCallbacks
     def _manageComments(self, client, mb_data, service, node, item_id, access=None):
@@ -517,9 +658,13 @@
         # FIXME: if 'comments' already exists in mb_data, it is not used to create the Node
         allow_comments = C.bool(mb_data.pop("allow_comments", "false"))
         if not allow_comments:
-            if 'comments' in mb_data:
-                log.warning(u"comments are not allowed but there is already a comments node, it may be lost: {uri}".format(uri=mb_data['comments']))
-                del mb_data['comments']
+            if "comments" in mb_data:
+                log.warning(
+                    u"comments are not allowed but there is already a comments node, it may be lost: {uri}".format(
+                        uri=mb_data["comments"]
+                    )
+                )
+                del mb_data["comments"]
             return
 
         if access is None:
@@ -527,60 +672,75 @@
             parent_node_config = yield self._p.getConfiguration(client, service, node)
             access = parent_node_config.get(self._p.OPT_ACCESS_MODEL, self._p.ACCESS_OPEN)
 
-        options = {self._p.OPT_ACCESS_MODEL: access,
-                   self._p.OPT_PERSIST_ITEMS: 1,
-                   self._p.OPT_MAX_ITEMS: -1,
-                   self._p.OPT_DELIVER_PAYLOADS: 1,
-                   self._p.OPT_SEND_ITEM_SUBSCRIBE: 1,
-                   # FIXME: would it make sense to restrict publish model to subscribers?
-                   self._p.OPT_PUBLISH_MODEL: self._p.ACCESS_OPEN,
-                   }
+        options = {
+            self._p.OPT_ACCESS_MODEL: access,
+            self._p.OPT_PERSIST_ITEMS: 1,
+            self._p.OPT_MAX_ITEMS: -1,
+            self._p.OPT_DELIVER_PAYLOADS: 1,
+            self._p.OPT_SEND_ITEM_SUBSCRIBE: 1,
+            # FIXME: would it make sense to restrict publish model to subscribers?
+            self._p.OPT_PUBLISH_MODEL: self._p.ACCESS_OPEN,
+        }
 
         # if other plugins need to change the options
         yield self.host.trigger.point("XEP-0277_comments", client, mb_data, options)
 
         try:
-            comments_node = mb_data['comments_node']
+            comments_node = mb_data["comments_node"]
         except KeyError:
             comments_node = self.getCommentsNode(item_id)
         else:
             if not comments_node:
-                raise exceptions.DataError(u"if comments_node is present, it must not be empty")
+                raise exceptions.DataError(
+                    u"if comments_node is present, it must not be empty"
+                )
 
         try:
-            comments_service = jid.JID(mb_data['comments_service'])
+            comments_service = jid.JID(mb_data["comments_service"])
         except KeyError:
             comments_service = yield self.getCommentsService(client, service)
 
         try:
             yield self._p.createNode(client, comments_service, comments_node, options)
         except error.StanzaError as e:
-            if e.condition == 'conflict':
-                log.info(u"node {} already exists on service {}".format(comments_node, comments_service))
+            if e.condition == "conflict":
+                log.info(
+                    u"node {} already exists on service {}".format(
+                        comments_node, comments_service
+                    )
+                )
             else:
                 raise e
         else:
             if access == self._p.ACCESS_WHITELIST:
                 # for whitelist access we need to copy affiliations from parent item
-                comments_affiliations = yield self._p.getNodeAffiliations(client, service, node)
+                comments_affiliations = yield self._p.getNodeAffiliations(
+                    client, service, node
+                )
                 # …except for "member", that we transform to publisher
                 # because we wants members to be able to write to comments
                 for jid_, affiliation in comments_affiliations.items():
-                    if affiliation == 'member':
-                        comments_affiliations[jid_] == 'publisher'
+                    if affiliation == "member":
+                        comments_affiliations[jid_] == "publisher"
 
-                yield self._p.setNodeAffiliations(client, comments_service, comments_node, comments_affiliations)
+                yield self._p.setNodeAffiliations(
+                    client, comments_service, comments_node, comments_affiliations
+                )
 
         if comments_service is None:
             comments_service = client.jid.userhostJID()
 
-        if 'comments' in mb_data:
-            if not mb_data['comments']:
-                raise exceptions.DataError(u"if comments is present, it must not be empty")
-            if 'comments_node' in mb_data or 'comments_service' in mb_data:
-                raise exceptions.DataError(u"You can't use comments_service/comments_node and comments at the same time")
+        if "comments" in mb_data:
+            if not mb_data["comments"]:
+                raise exceptions.DataError(
+                    u"if comments is present, it must not be empty"
+                )
+            if "comments_node" in mb_data or "comments_service" in mb_data:
+                raise exceptions.DataError(
+                    u"You can't use comments_service/comments_node and comments at the same time"
+                )
         else:
-            mb_data['comments'] = self._p.getNodeURI(comments_service, comments_node)
+            mb_data["comments"] = self._p.getNodeURI(comments_service, comments_node)
 
     def _mbSend(self, service, node, data, profile_key):
         service = jid.JID(service) if service else None
@@ -604,7 +764,7 @@
         if node is None:
             node = NS_MICROBLOG
 
-        item_id = data.get('id') or unicode(shortuuid.uuid())
+        item_id = data.get("id") or unicode(shortuuid.uuid())
 
         try:
             yield self._manageComments(client, data, service, node, item_id, access=None)
@@ -618,11 +778,25 @@
 
     def _mbRetract(self, service_jid_s, nodeIdentifier, itemIdentifier, profile_key):
         """Call self._p._retractItem, but use default node if node is empty"""
-        return self._p._retractItem(service_jid_s, nodeIdentifier or NS_MICROBLOG, itemIdentifier, True, profile_key)
+        return self._p._retractItem(
+            service_jid_s,
+            nodeIdentifier or NS_MICROBLOG,
+            itemIdentifier,
+            True,
+            profile_key,
+        )
 
     ## get ##
 
-    def _mbGet(self, service='', node='', max_items=10, item_ids=None, extra_dict=None, profile_key=C.PROF_KEY_NONE):
+    def _mbGet(
+        self,
+        service="",
+        node="",
+        max_items=10,
+        item_ids=None,
+        extra_dict=None,
+        profile_key=C.PROF_KEY_NONE,
+    ):
         """
         @param max_items(int): maximum number of item to get, C.NO_LIMIT for no limit
         @param item_ids (list[unicode]): list of item IDs
@@ -631,11 +805,27 @@
         service = jid.JID(service) if service else None
         max_items = None if max_items == C.NO_LIMIT else max_items
         extra = self._p.parseExtra(extra_dict)
-        return self.mbGet(client, service, node or None, max_items, item_ids, extra.rsm_request, extra.extra)
-
+        return self.mbGet(
+            client,
+            service,
+            node or None,
+            max_items,
+            item_ids,
+            extra.rsm_request,
+            extra.extra,
+        )
 
     @defer.inlineCallbacks
-    def mbGet(self, client, service=None, node=None, max_items=10, item_ids=None, rsm_request=None, extra=None):
+    def mbGet(
+        self,
+        client,
+        service=None,
+        node=None,
+        max_items=10,
+        item_ids=None,
+        rsm_request=None,
+        extra=None,
+    ):
         """Get some microblogs
 
         @param service(jid.JID, None): jid of the publisher
@@ -650,7 +840,15 @@
         """
         if node is None:
             node = NS_MICROBLOG
-        items_data = yield self._p.getItems(client, service, node, max_items=max_items, item_ids=item_ids, rsm_request=rsm_request, extra=extra)
+        items_data = yield self._p.getItems(
+            client,
+            service,
+            node,
+            max_items=max_items,
+            item_ids=item_ids,
+            rsm_request=rsm_request,
+            extra=extra,
+        )
         serialised = yield self._p.serItemsDataD(items_data, self.item2mbdata)
         defer.returnValue(serialised)
 
@@ -663,13 +861,13 @@
         will return(JID(u'sat-pubsub.example.net'), 'urn:xmpp:comments:_af43b363-3259-4b2a-ba4c-1bc33aa87634__urn:xmpp:groupblog:somebody@example.net')
         @return (tuple[jid.JID, unicode]): service and node
         """
-        parsed_url = urlparse.urlparse(node_url, 'xmpp')
+        parsed_url = urlparse.urlparse(node_url, "xmpp")
         service = jid.JID(parsed_url.path)
-        parsed_queries = urlparse.parse_qs(parsed_url.query.encode('utf-8'))
-        node = parsed_queries.get('node', [''])[0].decode('utf-8')
+        parsed_queries = urlparse.parse_qs(parsed_url.query.encode("utf-8"))
+        node = parsed_queries.get("node", [""])[0].decode("utf-8")
 
         if not node:
-            raise failure.Failure(exceptions.DataError('Invalid comments link'))
+            raise failure.Failure(exceptions.DataError("Invalid comments link"))
 
         return (service, node)
 
@@ -682,35 +880,49 @@
         @param access: Node access model, according to xep-0060 #4.5
         @param profile_key: profile key
         """
-        # FIXME: check if this mehtod is need, deprecate it if not
+        #  FIXME: check if this mehtod is need, deprecate it if not
         client = self.host.getClient(profile_key)
 
-        _options = {self._p.OPT_ACCESS_MODEL: access, self._p.OPT_PERSIST_ITEMS: 1, self._p.OPT_MAX_ITEMS: -1, self._p.OPT_DELIVER_PAYLOADS: 1, self._p.OPT_SEND_ITEM_SUBSCRIBE: 1}
+        _options = {
+            self._p.OPT_ACCESS_MODEL: access,
+            self._p.OPT_PERSIST_ITEMS: 1,
+            self._p.OPT_MAX_ITEMS: -1,
+            self._p.OPT_DELIVER_PAYLOADS: 1,
+            self._p.OPT_SEND_ITEM_SUBSCRIBE: 1,
+        }
 
         def cb(result):
-            #Node is created with right permission
+            # Node is created with right permission
             log.debug(_(u"Microblog node has now access %s") % access)
 
         def fatal_err(s_error):
-            #Something went wrong
+            # Something went wrong
             log.error(_(u"Can't set microblog access"))
             raise NodeAccessChangeException()
 
         def err_cb(s_error):
-            #If the node already exists, the condition is "conflict",
-            #else we have an unmanaged error
-            if s_error.value.condition == 'conflict':
-                #d = self.host.plugins["XEP-0060"].deleteNode(client, client.jid.userhostJID(), NS_MICROBLOG)
-                #d.addCallback(lambda x: create_node().addCallback(cb).addErrback(fatal_err))
+            # If the node already exists, the condition is "conflict",
+            # else we have an unmanaged error
+            if s_error.value.condition == "conflict":
+                # d = self.host.plugins["XEP-0060"].deleteNode(client, client.jid.userhostJID(), NS_MICROBLOG)
+                # d.addCallback(lambda x: create_node().addCallback(cb).addErrback(fatal_err))
                 change_node_options().addCallback(cb).addErrback(fatal_err)
             else:
                 fatal_err(s_error)
 
         def create_node():
-            return self._p.createNode(client, client.jid.userhostJID(), NS_MICROBLOG, _options)
+            return self._p.createNode(
+                client, client.jid.userhostJID(), NS_MICROBLOG, _options
+            )
 
         def change_node_options():
-            return self._p.setOptions(client.jid.userhostJID(), NS_MICROBLOG, client.jid.userhostJID(), _options, profile_key=profile_key)
+            return self._p.setOptions(
+                client.jid.userhostJID(),
+                NS_MICROBLOG,
+                client.jid.userhostJID(),
+                _options,
+                profile_key=profile_key,
+            )
 
         create_node().addCallback(cb).addErrback(err_cb)
 
@@ -735,12 +947,17 @@
             jids_set = client.roster.getJidsSet(publishers_type, publishers)
             if publishers_type == C.ALL:
                 try:  # display messages from salut-a-toi@libervia.org or other PEP services
-                    services = self.host.plugins["EXTRA-PEP"].getFollowedEntities(profile_key)
+                    services = self.host.plugins["EXTRA-PEP"].getFollowedEntities(
+                        profile_key
+                    )
                 except KeyError:
                     pass  # plugin is not loaded
                 else:
                     if services:
-                        log.debug("Extra PEP followed entities: %s" % ", ".join([unicode(service) for service in services]))
+                        log.debug(
+                            "Extra PEP followed entities: %s"
+                            % ", ".join([unicode(service) for service in services])
+                        )
                         jids_set.update(services)
 
         node_data = []
@@ -757,7 +974,11 @@
         """
         if publishers_type == C.ALL:
             if publishers:
-                raise failure.Failure(ValueError("Can't use publishers with {} type".format(publishers_type)))
+                raise failure.Failure(
+                    ValueError(
+                        "Can't use publishers with {} type".format(publishers_type)
+                    )
+                )
             else:
                 publishers = None
         elif publishers_type == C.JID:
@@ -785,8 +1006,12 @@
         @param profile: %(doc_profile)s
         @return (str): session id
         """
-        client, node_data = self._getClientAndNodeData(publishers_type, publishers, profile_key)
-        return self._p.subscribeToMany(node_data, client.jid.userhostJID(), profile_key=profile_key)
+        client, node_data = self._getClientAndNodeData(
+            publishers_type, publishers, profile_key
+        )
+        return self._p.subscribeToMany(
+            node_data, client.jid.userhostJID(), profile_key=profile_key
+        )
 
     # get #
 
@@ -804,32 +1029,65 @@
                 - items_metadata(dict): metadata as returned by [mbGet]
         @param profile_key: %(doc_profile_key)s
         """
+
         def onSuccess(items_data):
             """convert items elements to list of microblog data in items_data"""
             d = self._p.serItemsDataD(items_data, self.item2mbdata)
-            d.addCallback(lambda serialised:('', serialised))
+            d.addCallback(lambda serialised: ("", serialised))
             return d
 
         profile = self.host.getClient(profile_key).profile
-        d = self._p.getRTResults(session_id,
-                                 on_success = onSuccess,
-                                 on_error = lambda failure: (unicode(failure.value), ([],{})),
-                                 profile = profile)
-        d.addCallback(lambda ret: (ret[0],
-                                   [(service.full(), node, failure, items, metadata)
-                                    for (service, node), (success, (failure, (items, metadata))) in ret[1].iteritems()]))
+        d = self._p.getRTResults(
+            session_id,
+            on_success=onSuccess,
+            on_error=lambda failure: (unicode(failure.value), ([], {})),
+            profile=profile,
+        )
+        d.addCallback(
+            lambda ret: (
+                ret[0],
+                [
+                    (service.full(), node, failure, items, metadata)
+                    for (service, node), (success, (failure, (items, metadata))) in ret[
+                        1
+                    ].iteritems()
+                ],
+            )
+        )
         return d
 
-    def _mbGetFromMany(self, publishers_type, publishers, max_items=10, extra_dict=None, profile_key=C.PROF_KEY_NONE):
+    def _mbGetFromMany(
+        self,
+        publishers_type,
+        publishers,
+        max_items=10,
+        extra_dict=None,
+        profile_key=C.PROF_KEY_NONE,
+    ):
         """
         @param max_items(int): maximum number of item to get, C.NO_LIMIT for no limit
         """
         max_items = None if max_items == C.NO_LIMIT else max_items
         publishers_type, publishers = self._checkPublishers(publishers_type, publishers)
         extra = self._p.parseExtra(extra_dict)
-        return self.mbGetFromMany(publishers_type, publishers, max_items, extra.rsm_request, extra.extra, profile_key)
+        return self.mbGetFromMany(
+            publishers_type,
+            publishers,
+            max_items,
+            extra.rsm_request,
+            extra.extra,
+            profile_key,
+        )
 
-    def mbGetFromMany(self, publishers_type, publishers, max_items=None, rsm_request=None, extra=None, profile_key=C.PROF_KEY_NONE):
+    def mbGetFromMany(
+        self,
+        publishers_type,
+        publishers,
+        max_items=None,
+        rsm_request=None,
+        extra=None,
+        profile_key=C.PROF_KEY_NONE,
+    ):
         """Get the published microblogs for a list of groups or jids
 
         @param publishers_type (str): type of the list of publishers (one of "GROUP" or "JID" or "ALL")
@@ -841,12 +1099,18 @@
         @return (str): RT Deferred session id
         """
         # XXX: extra is unused here so far
-        client, node_data = self._getClientAndNodeData(publishers_type, publishers, profile_key)
-        return self._p.getFromMany(node_data, max_items, rsm_request, profile_key=profile_key)
+        client, node_data = self._getClientAndNodeData(
+            publishers_type, publishers, profile_key
+        )
+        return self._p.getFromMany(
+            node_data, max_items, rsm_request, profile_key=profile_key
+        )
 
     # comments #
 
-    def _mbGetFromManyWithCommentsRTResult(self, session_id, profile_key=C.PROF_KEY_DEFAULT):
+    def _mbGetFromManyWithCommentsRTResult(
+        self, session_id, profile_key=C.PROF_KEY_DEFAULT
+    ):
         """Get real-time results for [mbGetFromManyWithComments] session
 
         @param session_id: id of the real-time deferred session
@@ -869,12 +1133,29 @@
         """
         profile = self.host.getClient(profile_key).profile
         d = self.rt_sessions.getResults(session_id, profile=profile)
-        d.addCallback(lambda ret: (ret[0],
-                                   [(service.full(), node, failure, items, metadata)
-                                    for (service, node), (success, (failure, (items, metadata))) in ret[1].iteritems()]))
+        d.addCallback(
+            lambda ret: (
+                ret[0],
+                [
+                    (service.full(), node, failure, items, metadata)
+                    for (service, node), (success, (failure, (items, metadata))) in ret[
+                        1
+                    ].iteritems()
+                ],
+            )
+        )
         return d
 
-    def _mbGetFromManyWithComments(self, publishers_type, publishers, max_items=10, max_comments=C.NO_LIMIT, extra_dict=None, extra_comments_dict=None, profile_key=C.PROF_KEY_NONE):
+    def _mbGetFromManyWithComments(
+        self,
+        publishers_type,
+        publishers,
+        max_items=10,
+        max_comments=C.NO_LIMIT,
+        extra_dict=None,
+        extra_comments_dict=None,
+        profile_key=C.PROF_KEY_NONE,
+    ):
         """
         @param max_items(int): maximum number of item to get, C.NO_LIMIT for no limit
         @param max_comments(int): maximum number of comments to get, C.NO_LIMIT for no limit
@@ -884,14 +1165,30 @@
         publishers_type, publishers = self._checkPublishers(publishers_type, publishers)
         extra = self._p.parseExtra(extra_dict)
         extra_comments = self._p.parseExtra(extra_comments_dict)
-        return self.mbGetFromManyWithComments(publishers_type, publishers, max_items, max_comments or None,
-                                              extra.rsm_request,
-                                              extra.extra,
-                                              extra_comments.rsm_request,
-                                              extra_comments.extra,
-                                              profile_key)
+        return self.mbGetFromManyWithComments(
+            publishers_type,
+            publishers,
+            max_items,
+            max_comments or None,
+            extra.rsm_request,
+            extra.extra,
+            extra_comments.rsm_request,
+            extra_comments.extra,
+            profile_key,
+        )
 
-    def mbGetFromManyWithComments(self, publishers_type, publishers, max_items=None, max_comments=None, rsm_request=None, extra=None, rsm_comments=None, extra_comments=None, profile_key=C.PROF_KEY_NONE):
+    def mbGetFromManyWithComments(
+        self,
+        publishers_type,
+        publishers,
+        max_items=None,
+        max_comments=None,
+        rsm_request=None,
+        extra=None,
+        rsm_comments=None,
+        extra_comments=None,
+        profile_key=C.PROF_KEY_NONE,
+    ):
         """Helper method to get the microblogs and their comments in one shot
 
         @param publishers_type (str): type of the list of publishers (one of "GROUP" or "JID" or "ALL")
@@ -909,7 +1206,9 @@
         #      to serialise and associate the data, but it make life in frontends side
         #      a lot easier
 
-        client, node_data = self._getClientAndNodeData(publishers_type, publishers, profile_key)
+        client, node_data = self._getClientAndNodeData(
+            publishers_type, publishers, profile_key
+        )
 
         def getComments(items_data):
             """Retrieve comments and add them to the items_data
@@ -919,29 +1218,50 @@
                 with a list of comments data (service, node, list of items, metadata)
             """
             items, metadata = items_data
-            items_dlist = [] # deferred list for items
+            items_dlist = []  # deferred list for items
             for item in items:
-                dlist = [] # deferred list for comments
+                dlist = []  # deferred list for comments
                 for key, value in item.iteritems():
                     # we look for comments
-                    if key.startswith('comments') and key.endswith('_service'):
-                        prefix = key[:key.find('_')]
+                    if key.startswith("comments") and key.endswith("_service"):
+                        prefix = key[: key.find("_")]
                         service_s = value
                         node = item["{}{}".format(prefix, "_node")]
                         # time to get the comments
-                        d = self._p.getItems(client, jid.JID(service_s), node, max_comments, rsm_request=rsm_comments, extra=extra_comments)
+                        d = self._p.getItems(
+                            client,
+                            jid.JID(service_s),
+                            node,
+                            max_comments,
+                            rsm_request=rsm_comments,
+                            extra=extra_comments,
+                        )
                         # then serialise
-                        d.addCallback(lambda items_data: self._p.serItemsDataD(items_data, self.item2mbdata))
+                        d.addCallback(
+                            lambda items_data: self._p.serItemsDataD(
+                                items_data, self.item2mbdata
+                            )
+                        )
                         # with failure handling
-                        d.addCallback(lambda serialised_items_data: ('',) + serialised_items_data)
+                        d.addCallback(
+                            lambda serialised_items_data: ("",) + serialised_items_data
+                        )
                         d.addErrback(lambda failure: (unicode(failure.value), [], {}))
                         # and associate with service/node (needed if there are several comments nodes)
-                        d.addCallback(lambda serialised, service_s=service_s, node=node: (service_s, node) + serialised)
+                        d.addCallback(
+                            lambda serialised, service_s=service_s, node=node: (
+                                service_s,
+                                node,
+                            )
+                            + serialised
+                        )
                         dlist.append(d)
                 # we get the comments
                 comments_d = defer.gatherResults(dlist)
                 # and add them to the item data
-                comments_d.addCallback(lambda comments_data, item=item: (item, comments_data))
+                comments_d.addCallback(
+                    lambda comments_data, item=item: (item, comments_data)
+                )
                 items_dlist.append(comments_d)
             # we gather the items + comments in a list
             items_d = defer.gatherResults(items_dlist)
@@ -951,11 +1271,15 @@
 
         deferreds = {}
         for service, node in node_data:
-            d = deferreds[(service, node)] = self._p.getItems(client, service, node, max_items, rsm_request=rsm_request, extra=extra)
-            d.addCallback(lambda items_data: self._p.serItemsDataD(items_data, self.item2mbdata))
+            d = deferreds[(service, node)] = self._p.getItems(
+                client, service, node, max_items, rsm_request=rsm_request, extra=extra
+            )
+            d.addCallback(
+                lambda items_data: self._p.serItemsDataD(items_data, self.item2mbdata)
+            )
             d.addCallback(getComments)
-            d.addCallback(lambda items_comments_data: ('', items_comments_data))
-            d.addErrback(lambda failure: (unicode(failure.value), ([],{})))
+            d.addCallback(lambda items_comments_data: ("", items_comments_data))
+            d.addErrback(lambda failure: (unicode(failure.value), ([], {})))
 
         return self.rt_sessions.newSession(deferreds, client.profile)
 
@@ -963,8 +1287,8 @@
 class XEP_0277_handler(XMPPHandler):
     implements(iwokkel.IDisco)
 
-    def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
+    def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
         return [disco.DiscoFeature(NS_MICROBLOG)]
 
-    def getDiscoItems(self, requestor, target, nodeIdentifier=''):
+    def getDiscoItems(self, requestor, target, nodeIdentifier=""):
         return []
--- a/sat/plugins/plugin_xep_0280.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_xep_0280.py	Wed Jun 27 20:14:46 2018 +0200
@@ -19,6 +19,7 @@
 
 from sat.core.i18n import _, D_
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from sat.core import exceptions
 from sat.core.constants import Const as C
@@ -27,6 +28,7 @@
 from twisted.internet import defer
 from wokkel import disco, iwokkel
 from zope.interface import implements
+
 try:
     from twisted.words.protocols.xmlstream import XMPPHandler
 except ImportError:
@@ -36,7 +38,7 @@
 PARAM_CATEGORY = "Misc"
 PARAM_NAME = "carbon"
 PARAM_LABEL = D_(u"Message carbons")
-NS_CARBONS = 'urn:xmpp:carbons:2'
+NS_CARBONS = "urn:xmpp:carbons:2"
 
 PLUGIN_INFO = {
     C.PI_NAME: u"XEP-0280 Plugin",
@@ -46,12 +48,12 @@
     C.PI_DEPENDENCIES: [],
     C.PI_MAIN: u"XEP_0280",
     C.PI_HANDLER: u"yes",
-    C.PI_DESCRIPTION: D_(u"""Implementation of Message Carbons""")
+    C.PI_DESCRIPTION: D_(u"""Implementation of Message Carbons"""),
 }
 
 
 class XEP_0280(object):
-    # TODO: param is only checked at profile connection
+    #  TODO: param is only checked at profile connection
     #       activate carbons on param change even after profile connection
     # TODO: chat state notifications are not handled yet (and potentially other XEPs?)
 
@@ -64,11 +66,11 @@
     </individual>
     </params>
     """.format(
-        category_name = PARAM_CATEGORY,
-        category_label = D_(PARAM_CATEGORY),
-        param_name = PARAM_NAME,
-        param_label = PARAM_LABEL,
-        )
+        category_name=PARAM_CATEGORY,
+        category_label=D_(PARAM_CATEGORY),
+        param_name=PARAM_NAME,
+        param_label=PARAM_LABEL,
+    )
 
     def __init__(self, host):
         log.info(_("Plugin XEP_0280 initialization"))
@@ -86,15 +88,17 @@
         (in particular end 2 end encryption plugins)
         @param message_elt(domish.Element): <message> stanza
         """
-        if message_elt.name != u'message':
+        if message_elt.name != u"message":
             log.error(u"addPrivateElt must be used with <message> stanzas")
             return
-        message_elt.addElement((NS_CARBONS, u'private'))
+        message_elt.addElement((NS_CARBONS, u"private"))
 
     @defer.inlineCallbacks
     def profileConnected(self, client):
         """activate message carbons on connection if possible and activated in config"""
-        activate = self.host.memory.getParamA(PARAM_NAME, PARAM_CATEGORY, profile_key=client.profile)
+        activate = self.host.memory.getParamA(
+            PARAM_NAME, PARAM_CATEGORY, profile_key=client.profile
+        )
         if not activate:
             log.info(_(u"Not activating message carbons as requested in params"))
             return
@@ -105,7 +109,7 @@
         else:
             log.info(_(u"message carbons available, enabling it"))
             iq_elt = client.IQ()
-            iq_elt.addElement((NS_CARBONS, 'enable'))
+            iq_elt.addElement((NS_CARBONS, "enable"))
             try:
                 yield iq_elt.send()
             except StanzaError as e:
@@ -126,40 +130,45 @@
             # we continue normal behaviour
             return True
 
-        if message_elt['from'] != client.jid.userhost():
-            log.warning(u"The message carbon received is not from our server, hack attempt?\n{xml}".format(
-                xml = message_elt.toXml(),
-                ))
+        if message_elt["from"] != client.jid.userhost():
+            log.warning(
+                u"The message carbon received is not from our server, hack attempt?\n{xml}".format(
+                    xml=message_elt.toXml()
+                )
+            )
             return
-        forwarded_elt = next(carbons_elt.elements(C.NS_FORWARD, 'forwarded'))
-        cc_message_elt = next(forwarded_elt.elements(C.NS_CLIENT, 'message'))
-        if carbons_elt.name == 'received':
+        forwarded_elt = next(carbons_elt.elements(C.NS_FORWARD, "forwarded"))
+        cc_message_elt = next(forwarded_elt.elements(C.NS_CLIENT, "message"))
+        if carbons_elt.name == "received":
             # on receive we replace the wrapping message with the CCed one
             # and continue the normal behaviour
-            message_elt['from'] = cc_message_elt['from']
+            message_elt["from"] = cc_message_elt["from"]
             del message_elt.children[:]
             for c in cc_message_elt.children:
                 message_elt.addChild(c)
             return True
-        elif carbons_elt.name == 'sent':
+        elif carbons_elt.name == "sent":
             # on send we parse the message and just add it to history
             # and send it to frontends (without normal sending treatments)
             mess_data = SatMessageProtocol.parseMessage(cc_message_elt, client)
-            if not mess_data['message'] and not mess_data['subject']:
+            if not mess_data["message"] and not mess_data["subject"]:
                 return False
             client.messageAddToHistory(mess_data)
             client.messageSendToBridge(mess_data)
         else:
-            log.warning(u"invalid message carbons received:\n{xml}".format(
-                xml = message_elt.toXml()))
+            log.warning(
+                u"invalid message carbons received:\n{xml}".format(
+                    xml=message_elt.toXml()
+                )
+            )
         return False
 
 
 class XEP_0280_handler(XMPPHandler):
     implements(iwokkel.IDisco)
 
-    def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
+    def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
         return [disco.DiscoFeature(NS_CARBONS)]
 
-    def getDiscoItems(self, requestor, target, nodeIdentifier=''):
+    def getDiscoItems(self, requestor, target, nodeIdentifier=""):
         return []
--- a/sat/plugins/plugin_xep_0297.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_xep_0297.py	Wed Jun 27 20:14:46 2018 +0200
@@ -21,9 +21,11 @@
 from sat.core.constants import Const as C
 from sat.core.i18n import _, D_
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 
 from wokkel import disco, iwokkel
+
 try:
     from twisted.words.protocols.xmlstream import XMPPHandler
 except ImportError:
@@ -41,7 +43,7 @@
     C.PI_PROTOCOLS: [u"XEP-0297"],
     C.PI_MAIN: "XEP_0297",
     C.PI_HANDLER: u"yes",
-    C.PI_DESCRIPTION: D_(u"""Implementation of Stanza Forwarding""")
+    C.PI_DESCRIPTION: D_(u"""Implementation of Stanza Forwarding"""),
 }
 
 
@@ -70,7 +72,7 @@
             if isinstance(child, domish.Element) and not child.uri:
                 XEP_0297.updateUri(child, uri)
 
-    def forward(self, stanza, to_jid, stamp, body='', profile_key=C.PROF_KEY_NONE):
+    def forward(self, stanza, to_jid, stamp, body="", profile_key=C.PROF_KEY_NONE):
         """Forward a message to the given JID.
 
         @param stanza (domish.Element): original stanza to be forwarded.
@@ -82,27 +84,29 @@
         """
         # FIXME: this method is not used and doesn't use mess_data which should be used for client.sendMessageData
         #        should it be deprecated? A method constructing the element without sending it seems more natural
-        log.warning(u"THIS METHOD IS DEPRECATED") # FIXME: we use this warning until we check the method
-        msg = domish.Element((None, 'message'))
-        msg['to'] = to_jid.full()
-        msg['type'] = stanza['type']
+        log.warning(
+            u"THIS METHOD IS DEPRECATED"
+        )  #  FIXME: we use this warning until we check the method
+        msg = domish.Element((None, "message"))
+        msg["to"] = to_jid.full()
+        msg["type"] = stanza["type"]
 
-        body_elt = domish.Element((None, 'body'))
+        body_elt = domish.Element((None, "body"))
         if body:
             body_elt.addContent(body)
 
-        forwarded_elt = domish.Element((NS_SF, 'forwarded'))
-        delay_elt = self.host.plugins['XEP-0203'].delay(stamp)
+        forwarded_elt = domish.Element((NS_SF, "forwarded"))
+        delay_elt = self.host.plugins["XEP-0203"].delay(stamp)
         forwarded_elt.addChild(delay_elt)
         if not stanza.uri:  # None or ''
-            XEP_0297.updateUri(stanza, 'jabber:client')
+            XEP_0297.updateUri(stanza, "jabber:client")
         forwarded_elt.addChild(stanza)
 
         msg.addChild(body_elt)
         msg.addChild(forwarded_elt)
 
         client = self.host.getClient(profile_key)
-        return client.sendMessageData({u'xml': msg})
+        return client.sendMessageData({u"xml": msg})
 
 
 class XEP_0297_handler(XMPPHandler):
@@ -113,8 +117,8 @@
         self.host = plugin_parent.host
         self.profile = profile
 
-    def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
+    def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
         return [disco.DiscoFeature(NS_SF)]
 
-    def getDiscoItems(self, requestor, target, nodeIdentifier=''):
+    def getDiscoItems(self, requestor, target, nodeIdentifier=""):
         return []
--- a/sat/plugins/plugin_xep_0300.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_xep_0300.py	Wed Jun 27 20:14:46 2018 +0200
@@ -20,6 +20,7 @@
 from sat.core.i18n import _
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from sat.core import exceptions
 from twisted.words.xish import domish
@@ -41,27 +42,29 @@
     C.PI_PROTOCOLS: ["XEP-0300"],
     C.PI_MAIN: "XEP_0300",
     C.PI_HANDLER: "yes",
-    C.PI_DESCRIPTION: _("""Management of cryptographic hashes""")
+    C.PI_DESCRIPTION: _("""Management of cryptographic hashes"""),
 }
 
 NS_HASHES = "urn:xmpp:hashes:2"
 NS_HASHES_FUNCTIONS = u"urn:xmpp:hash-function-text-names:{}"
-BUFFER_SIZE = 2**12
-ALGO_DEFAULT = 'sha-256'
+BUFFER_SIZE = 2 ** 12
+ALGO_DEFAULT = "sha-256"
 
 
 class XEP_0300(object):
     # TODO: add blake after moving to Python 3
-    ALGOS = OrderedDict((
-            (u'md5', hashlib.md5),
-            (u'sha-1', hashlib.sha1),
-            (u'sha-256', hashlib.sha256),
-            (u'sha-512', hashlib.sha512),
-            ))
+    ALGOS = OrderedDict(
+        (
+            (u"md5", hashlib.md5),
+            (u"sha-1", hashlib.sha1),
+            (u"sha-256", hashlib.sha256),
+            (u"sha-512", hashlib.sha512),
+        )
+    )
 
     def __init__(self, host):
         log.info(_("plugin Hashes initialization"))
-        host.registerNamespace('hashes', NS_HASHES)
+        host.registerNamespace("hashes", NS_HASHES)
 
     def getHandler(self, client):
         return XEP_0300_handler()
@@ -90,11 +93,15 @@
         """
         client = self.host.getClient(profile)
         for algo in reversed(XEP_0300.ALGOS):
-            has_feature = yield self.host.hasFeature(client, NS_HASHES_FUNCTIONS.format(algo), to_jid)
+            has_feature = yield self.host.hasFeature(
+                client, NS_HASHES_FUNCTIONS.format(algo), to_jid
+            )
             if has_feature:
-                log.debug(u"Best hashing algorithm found for {jid}: {algo}".format(
-                    jid=to_jid.full(),
-                    algo=algo))
+                log.debug(
+                    u"Best hashing algorithm found for {jid}: {algo}".format(
+                        jid=to_jid.full(), algo=algo
+                    )
+                )
                 defer.returnValue(algo)
 
     def _calculateHashBlocking(self, file_obj, hasher):
@@ -123,16 +130,18 @@
         @param algo(unicode): algorithme to use, must be a key of XEP_0300.ALGOS
         @return (D(domish.Element)): hash element
         """
+
         def hashCalculated(hash_):
             return self.buildHashElt(hash_, algo)
+
         hasher = self.ALGOS[algo]
         hash_d = self.calculateHash(file_obj, hasher)
         hash_d.addCallback(hashCalculated)
         return hash_d
 
     def buildHashUsedElt(self, algo=ALGO_DEFAULT):
-        hash_used_elt = domish.Element((NS_HASHES, 'hash-used'))
-        hash_used_elt['algo'] = algo
+        hash_used_elt = domish.Element((NS_HASHES, "hash-used"))
+        hash_used_elt["algo"] = algo
         return hash_used_elt
 
     def parseHashUsedElt(self, parent):
@@ -144,10 +153,10 @@
         @raise exceptions.DataError: the element is invalid
         """
         try:
-            hash_used_elt = next(parent.elements(NS_HASHES, 'hash-used'))
+            hash_used_elt = next(parent.elements(NS_HASHES, "hash-used"))
         except StopIteration:
             raise exceptions.NotFound
-        algo = hash_used_elt[u'algo']
+        algo = hash_used_elt[u"algo"]
         if not algo:
             raise exceptions.DataError
         return algo
@@ -161,10 +170,10 @@
         """
         assert hash_
         assert algo
-        hash_elt = domish.Element((NS_HASHES, 'hash'))
+        hash_elt = domish.Element((NS_HASHES, "hash"))
         if hash_ is not None:
             hash_elt.addContent(base64.b64encode(hash_))
-        hash_elt['algo'] = algo
+        hash_elt["algo"] = algo
         return hash_elt
 
     def parseHashElt(self, parent):
@@ -181,8 +190,8 @@
         hash_elt = None
         best_algo = None
         best_value = None
-        for hash_elt in parent.elements(NS_HASHES, 'hash'):
-            algo = hash_elt.getAttribute('algo')
+        for hash_elt in parent.elements(NS_HASHES, "hash"):
+            algo = hash_elt.getAttribute("algo")
             try:
                 idx = algos.index(algo)
             except ValueError:
@@ -204,9 +213,12 @@
 class XEP_0300_handler(XMPPHandler):
     implements(iwokkel.IDisco)
 
-    def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
-        hash_functions_names = [disco.DiscoFeature(NS_HASHES_FUNCTIONS.format(algo)) for algo in XEP_0300.ALGOS]
+    def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
+        hash_functions_names = [
+            disco.DiscoFeature(NS_HASHES_FUNCTIONS.format(algo))
+            for algo in XEP_0300.ALGOS
+        ]
         return [disco.DiscoFeature(NS_HASHES)] + hash_functions_names
 
-    def getDiscoItems(self, requestor, target, nodeIdentifier=''):
+    def getDiscoItems(self, requestor, target, nodeIdentifier=""):
         return []
--- a/sat/plugins/plugin_xep_0313.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_xep_0313.py	Wed Jun 27 20:14:46 2018 +0200
@@ -21,6 +21,7 @@
 from sat.core.constants import Const as C
 from sat.core.i18n import _
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from sat.core import exceptions
 
@@ -45,12 +46,11 @@
     C.PI_PROTOCOLS: ["XEP-0313"],
     C.PI_MAIN: "XEP_0313",
     C.PI_HANDLER: "yes",
-    C.PI_DESCRIPTION: _("""Implementation of Message Archive Management""")
+    C.PI_DESCRIPTION: _("""Implementation of Message Archive Management"""),
 }
 
 
 class XEP_0313(object):
-
     def __init__(self, host):
         log.info(_("Message Archive Management plugin initialization"))
         self.host = host
@@ -86,7 +86,7 @@
     def _queryFinished(self, iq_result, client, elt_list, event):
         client.xmlstream.removeObserver(event, self._appendMessage)
         try:
-            fin_elt = iq_result.elements(mam.NS_MAM, 'fin').next()
+            fin_elt = iq_result.elements(mam.NS_MAM, "fin").next()
         except StopIteration:
             raise exceptions.DataError(u"Invalid MAM result")
 
@@ -119,14 +119,21 @@
         # http://xmpp.org/extensions/xep-0313.html#prefs
         return client._mam.queryPrefs(service)
 
-    def _setPrefs(self, service_s=None, default='roster', always=None, never=None, profile_key=C.PROF_KEY_NONE):
+    def _setPrefs(
+        self,
+        service_s=None,
+        default="roster",
+        always=None,
+        never=None,
+        profile_key=C.PROF_KEY_NONE,
+    ):
         service = jid.JID(service_s) if service_s else None
         always_jid = [jid.JID(entity) for entity in always]
         never_jid = [jid.JID(entity) for entity in never]
-        #TODO: why not build here a MAMPrefs object instead of passing the args separately?
+        # TODO: why not build here a MAMPrefs object instead of passing the args separately?
         return self.setPrefs(service, default, always_jid, never_jid, profile_key)
 
-    def setPrefs(self, client, service=None, default='roster', always=None, never=None):
+    def setPrefs(self, client, service=None, default="roster", always=None, never=None):
         """Set news user preferences.
 
         @param service: entity offering the MAM service (None for user archives)
@@ -143,8 +150,8 @@
 class SatMAMClient(mam.MAMClient):
     implements(disco.IDisco)
 
-    def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
+    def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
         return [disco.DiscoFeature(mam.NS_MAM)]
 
-    def getDiscoItems(self, requestor, target, nodeIdentifier=''):
+    def getDiscoItems(self, requestor, target, nodeIdentifier=""):
         return []
--- a/sat/plugins/plugin_xep_0329.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_xep_0329.py	Wed Jun 27 20:14:46 2018 +0200
@@ -21,6 +21,7 @@
 from sat.core import exceptions
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from sat.tools import stream
 from sat.tools.common import regex
@@ -44,30 +45,35 @@
     C.PI_DEPENDENCIES: ["XEP-0234", "XEP-0300"],
     C.PI_MAIN: "XEP_0329",
     C.PI_HANDLER: "yes",
-    C.PI_DESCRIPTION: _(u"""Implementation of File Information Sharing""")
+    C.PI_DESCRIPTION: _(u"""Implementation of File Information Sharing"""),
 }
 
-NS_FIS = 'urn:xmpp:fis:0'
+NS_FIS = "urn:xmpp:fis:0"
 
 IQ_FIS_REQUEST = C.IQ_GET + '/query[@xmlns="' + NS_FIS + '"]'
 SINGLE_FILES_DIR = u"files"
-TYPE_VIRTUAL= u'virtual'
-TYPE_PATH = u'path'
-SHARE_TYPES  = (TYPE_PATH, TYPE_VIRTUAL)
-KEY_TYPE = u'type'
+TYPE_VIRTUAL = u"virtual"
+TYPE_PATH = u"path"
+SHARE_TYPES = (TYPE_PATH, TYPE_VIRTUAL)
+KEY_TYPE = u"type"
 
 
 class ShareNode(object):
     """node containing directory or files to share, virtual or real"""
+
     host = None
 
     def __init__(self, name, parent, type_, access, path=None):
         assert type_ in SHARE_TYPES
         if name is not None:
-            if name == u'..' or u'/' in name or u'\\' in name:
-                log.warning(_(u'path change chars found in name [{name}], hack attempt?').format(name=name))
-                if name == u'..':
-                    name = u'--'
+            if name == u".." or u"/" in name or u"\\" in name:
+                log.warning(
+                    _(u"path change chars found in name [{name}], hack attempt?").format(
+                        name=name
+                    )
+                )
+                if name == u"..":
+                    name = u"--"
                 else:
                     name = regex.pathEscape(name)
         self.name = name
@@ -125,7 +131,9 @@
         try:
             del self.parent.children[self.name]
         except TypeError:
-            raise exceptions.InternalError(u'trying to remove a node from inexisting parent')
+            raise exceptions.InternalError(
+                u"trying to remove a node from inexisting parent"
+            )
         except KeyError:
             raise exceptions.InternalError(u"node not found in parent's children")
         self.parent = None
@@ -138,7 +146,7 @@
         @param peer_jid(jid.JID): entity which try to access the node
         @return (bool): True if entity can access
         """
-        file_data = {u'access':self.access, u'owner': client.jid.userhostJID()}
+        file_data = {u"access": self.access, u"owner": client.jid.userhostJID()}
         try:
             self.host.memory.checkFilePermission(file_data, peer_jid, perms)
         except exceptions.PermissionError:
@@ -146,7 +154,9 @@
         else:
             return True
 
-    def checkPermissions(self, client, peer_jid, perms=(C.ACCESS_PERM_READ,), check_parents=True):
+    def checkPermissions(
+        self, client, peer_jid, perms=(C.ACCESS_PERM_READ,), check_parents=True
+    ):
         """check that peer_jid can access this node and all its parents
 
         @param peer_jid(jid.JID): entrity trying to access the node
@@ -178,15 +188,18 @@
         @raise exceptions.DataError: path is invalid
         @raise NotFound: path lead to a non existing file/directory
         """
-        path_elts = filter(None, path.split(u'/'))
+        path_elts = filter(None, path.split(u"/"))
 
-        if u'..' in path_elts:
-            log.warning(_(u'parent dir ("..") found in path, hack attempt? path is {path} [{profile}]').format(
-                path=path, profile=client.profile))
+        if u".." in path_elts:
+            log.warning(
+                _(
+                    u'parent dir ("..") found in path, hack attempt? path is {path} [{profile}]'
+                ).format(path=path, profile=client.profile)
+            )
             raise exceptions.PermissionError(u"illegal path elements")
 
         if not path_elts:
-            raise exceptions.DataError(_(u'path is invalid: {path}').format(path=path))
+            raise exceptions.DataError(_(u"path is invalid: {path}").format(path=path))
 
         node = client._XEP_0329_root_node
 
@@ -199,10 +212,10 @@
             elif node.type == TYPE_PATH:
                 break
 
-        if not node.checkPermissions(client, peer_jid, perms = perms):
+        if not node.checkPermissions(client, peer_jid, perms=perms):
             raise exceptions.PermissionError(u"permission denied")
 
-        return node, u'/'.join(path_elts)
+        return node, u"/".join(path_elts)
 
     def findByLocalPath(self, path):
         """retrieve nodes linking to local path
@@ -223,8 +236,9 @@
         elif node.type == TYPE_PATH:
             paths.setdefault(node.path, []).append(node)
         else:
-            raise exceptions.InternalError(u'unknown node type: {type}'.format(
-                type = node.type))
+            raise exceptions.InternalError(
+                u"unknown node type: {type}".format(type=node.type)
+            )
 
     def getSharedPaths(self):
         """retrieve nodes by shared path
@@ -233,38 +247,71 @@
         @return (dict): map from shared path to list of nodes
         """
         if self.type == TYPE_PATH:
-            raise exceptions.InternalError("getSharedPaths must be used on a virtual node")
+            raise exceptions.InternalError(
+                "getSharedPaths must be used on a virtual node"
+            )
         paths = {}
         self._getSharedPaths(self, paths)
         return paths
 
 
 class XEP_0329(object):
-
     def __init__(self, host):
         log.info(_("File Information Sharing initialization"))
         self.host = host
         ShareNode.host = host
-        self._h = host.plugins['XEP-0300']
-        self._jf = host.plugins['XEP-0234']
-        host.bridge.addMethod("FISList", ".plugin", in_sign='ssa{ss}s', out_sign='aa{ss}', method=self._listFiles, async=True)
-        host.bridge.addMethod("FISLocalSharesGet", ".plugin", in_sign='s', out_sign='as', method=self._localSharesGet)
-        host.bridge.addMethod("FISSharePath", ".plugin", in_sign='ssss', out_sign='s', method=self._sharePath)
-        host.bridge.addMethod("FISUnsharePath", ".plugin", in_sign='ss', out_sign='', method=self._unsharePath)
-        host.bridge.addSignal("FISSharedPathNew", ".plugin", signature='sss')
-        host.bridge.addSignal("FISSharedPathRemoved", ".plugin", signature='ss')
+        self._h = host.plugins["XEP-0300"]
+        self._jf = host.plugins["XEP-0234"]
+        host.bridge.addMethod(
+            "FISList",
+            ".plugin",
+            in_sign="ssa{ss}s",
+            out_sign="aa{ss}",
+            method=self._listFiles,
+            async=True,
+        )
+        host.bridge.addMethod(
+            "FISLocalSharesGet",
+            ".plugin",
+            in_sign="s",
+            out_sign="as",
+            method=self._localSharesGet,
+        )
+        host.bridge.addMethod(
+            "FISSharePath",
+            ".plugin",
+            in_sign="ssss",
+            out_sign="s",
+            method=self._sharePath,
+        )
+        host.bridge.addMethod(
+            "FISUnsharePath",
+            ".plugin",
+            in_sign="ss",
+            out_sign="",
+            method=self._unsharePath,
+        )
+        host.bridge.addSignal("FISSharedPathNew", ".plugin", signature="sss")
+        host.bridge.addSignal("FISSharedPathRemoved", ".plugin", signature="ss")
         host.trigger.add("XEP-0234_fileSendingRequest", self._fileSendingRequestTrigger)
-        host.registerNamespace('fis', NS_FIS)
+        host.registerNamespace("fis", NS_FIS)
 
     def getHandler(self, client):
         return XEP_0329_handler(self)
 
     def profileConnected(self, client):
         if not client.is_component:
-            client._XEP_0329_root_node = ShareNode(None, None, TYPE_VIRTUAL, {C.ACCESS_PERM_READ: {KEY_TYPE: C.ACCESS_TYPE_PUBLIC}})
-            client._XEP_0329_names_data = {} # name to share map
+            client._XEP_0329_root_node = ShareNode(
+                None,
+                None,
+                TYPE_VIRTUAL,
+                {C.ACCESS_PERM_READ: {KEY_TYPE: C.ACCESS_TYPE_PUBLIC}},
+            )
+            client._XEP_0329_names_data = {}  #  name to share map
 
-    def _fileSendingRequestTrigger(self, client, session, content_data, content_name, file_data, file_elt):
+    def _fileSendingRequestTrigger(
+        self, client, session, content_data, content_name, file_data, file_elt
+    ):
         """this trigger check that a requested file is available, and fill suitable data if so
 
         path and name are used to retrieve the file. If path is missing, we try our luck with known names
@@ -273,21 +320,21 @@
             return True, None
 
         try:
-            name = file_data[u'name']
+            name = file_data[u"name"]
         except KeyError:
             return True, None
-        assert u'/' not in name
+        assert u"/" not in name
 
-        path = file_data.get(u'path')
+        path = file_data.get(u"path")
         if path is not None:
             # we have a path, we can follow it to find node
             try:
-                node, rem_path = ShareNode.find(client, path, session[u'peer_jid'])
+                node, rem_path = ShareNode.find(client, path, session[u"peer_jid"])
             except (exceptions.PermissionError, exceptions.NotFound):
-                # no file, or file not allowed, we continue normal workflow
+                #  no file, or file not allowed, we continue normal workflow
                 return True, None
             except exceptions.DataError:
-                log.warning(_(u'invalid path: {path}').format(path=path))
+                log.warning(_(u"invalid path: {path}").format(path=path))
                 return True, None
 
             if node.type == TYPE_VIRTUAL:
@@ -300,7 +347,9 @@
                 # we have a path node, so we can retrieve the full path now
                 path = os.path.join(node.path, rem_path, name)
             else:
-                raise exceptions.InternalError(u'unknown type: {type}'.format(type=node.type))
+                raise exceptions.InternalError(
+                    u"unknown type: {type}".format(type=node.type)
+                )
             if not os.path.exists(path):
                 return True, None
             size = os.path.getsize(path)
@@ -312,49 +361,56 @@
                 return True, None
 
             for path, shared_file in name_data.iteritems():
-                if True:  # FIXME: filters are here
+                if True:  #  FIXME: filters are here
                     break
             else:
                 return True, None
-            parent_node = shared_file[u'parent']
-            if not parent_node.checkPermissions(client, session[u'peer_jid']):
-                log.warning(_(u"{peer_jid} requested a file (s)he can't access [{profile}]").format(
-                    peer_jid = session[u'peer_jid'], profile = client.profile))
+            parent_node = shared_file[u"parent"]
+            if not parent_node.checkPermissions(client, session[u"peer_jid"]):
+                log.warning(
+                    _(
+                        u"{peer_jid} requested a file (s)he can't access [{profile}]"
+                    ).format(peer_jid=session[u"peer_jid"], profile=client.profile)
+                )
                 return True, None
-            size = shared_file[u'size']
+            size = shared_file[u"size"]
 
-        file_data[u'size'] = size
-        file_elt.addElement(u'size', content=unicode(size))
-        hash_algo = file_data[u'hash_algo'] = self._h.getDefaultAlgo()
-        hasher = file_data[u'hash_hasher'] = self._h.getHasher(hash_algo)
+        file_data[u"size"] = size
+        file_elt.addElement(u"size", content=unicode(size))
+        hash_algo = file_data[u"hash_algo"] = self._h.getDefaultAlgo()
+        hasher = file_data[u"hash_hasher"] = self._h.getHasher(hash_algo)
         file_elt.addChild(self._h.buildHashUsedElt(hash_algo))
-        content_data['stream_object'] = stream.FileStreamObject(
+        content_data["stream_object"] = stream.FileStreamObject(
             self.host,
             client,
             path,
             uid=self._jf.getProgressId(session, content_name),
             size=size,
             data_cb=lambda data: hasher.update(data),
-            )
+        )
         return False, True
 
     # common methods
 
     def _requestHandler(self, client, iq_elt, root_nodes_cb, files_from_node_cb):
         iq_elt.handled = True
-        owner = jid.JID(iq_elt['from']).userhostJID()
-        node = iq_elt.query.getAttribute('node')
+        owner = jid.JID(iq_elt["from"]).userhostJID()
+        node = iq_elt.query.getAttribute("node")
         if not node:
             d = defer.maybeDeferred(root_nodes_cb, client, iq_elt, owner)
         else:
             d = defer.maybeDeferred(files_from_node_cb, client, iq_elt, owner, node)
-        d.addErrback(lambda failure_: log.error(_(u"error while retrieving files: {msg}").format(msg=failure_)))
+        d.addErrback(
+            lambda failure_: log.error(
+                _(u"error while retrieving files: {msg}").format(msg=failure_)
+            )
+        )
 
     def _iqError(self, client, iq_elt, condition="item-not-found"):
         error_elt = jabber_error.StanzaError(condition).toResponse(iq_elt)
         client.send(error_elt)
 
-    # client
+    #  client
 
     def _addPathData(self, client, query_elt, path, parent_node):
         """Fill query_elt with files/directories found in path"""
@@ -362,23 +418,24 @@
         if os.path.isfile(path):
             size = os.path.getsize(path)
             mime_type = mimetypes.guess_type(path, strict=False)[0]
-            file_elt = self._jf.buildFileElement(name = name,
-                                                 size = size,
-                                                 mime_type = mime_type,
-                                                 modified = os.path.getmtime(path))
+            file_elt = self._jf.buildFileElement(
+                name=name, size=size, mime_type=mime_type, modified=os.path.getmtime(path)
+            )
 
             query_elt.addChild(file_elt)
             # we don't specify hash as it would be too resource intensive to calculate it for all files
             # we add file to name_data, so users can request it later
             name_data = client._XEP_0329_names_data.setdefault(name, {})
             if path not in name_data:
-                name_data[path] = {'size': size,
-                                   'mime_type': mime_type,
-                                   'parent': parent_node}
+                name_data[path] = {
+                    "size": size,
+                    "mime_type": mime_type,
+                    "parent": parent_node,
+                }
         else:
             # we have a directory
-            directory_elt = query_elt.addElement('directory')
-            directory_elt['name'] = name
+            directory_elt = query_elt.addElement("directory")
+            directory_elt["name"] = name
 
     def _pathNodeHandler(self, client, iq_elt, query_elt, node, path):
         """Fill query_elt for path nodes, i.e. physical directories"""
@@ -390,13 +447,15 @@
         elif os.path.isfile(path):
             self._addPathData(client, query_elt, path, node)
         else:
-            for name in sorted(os.listdir(path.encode('utf-8')), key=lambda n: n.lower()):
+            for name in sorted(os.listdir(path.encode("utf-8")), key=lambda n: n.lower()):
                 try:
-                    name = name.decode('utf-8', 'strict')
+                    name = name.decode("utf-8", "strict")
                 except UnicodeDecodeError as e:
-                    log.warning(_(u"ignoring invalid unicode name ({name}): {msg}").format(
-                        name = name.decode('utf-8', 'replace'),
-                        msg = e))
+                    log.warning(
+                        _(u"ignoring invalid unicode name ({name}): {msg}").format(
+                            name=name.decode("utf-8", "replace"), msg=e
+                        )
+                    )
                     continue
                 full_path = os.path.join(path, name)
                 self._addPathData(client, query_elt, full_path, node)
@@ -408,68 +467,79 @@
                 continue
             node_type = child_node.type
             if node_type == TYPE_VIRTUAL:
-                directory_elt = query_elt.addElement('directory')
-                directory_elt['name'] = name
+                directory_elt = query_elt.addElement("directory")
+                directory_elt["name"] = name
             elif node_type == TYPE_PATH:
                 self._addPathData(client, query_elt, child_node.path, child_node)
             else:
-                raise exceptions.InternalError(_(u'unexpected type: {type}').format(type=node_type))
+                raise exceptions.InternalError(
+                    _(u"unexpected type: {type}").format(type=node_type)
+                )
 
     def _getRootNodesCb(self, client, iq_elt, owner):
-        peer_jid = jid.JID(iq_elt['from'])
-        iq_result_elt = xmlstream.toResponse(iq_elt, 'result')
-        query_elt = iq_result_elt.addElement((NS_FIS, 'query'))
+        peer_jid = jid.JID(iq_elt["from"])
+        iq_result_elt = xmlstream.toResponse(iq_elt, "result")
+        query_elt = iq_result_elt.addElement((NS_FIS, "query"))
         for name, node in client._XEP_0329_root_node.iteritems():
             if not node.checkPermissions(client, peer_jid, check_parents=False):
                 continue
-            directory_elt = query_elt.addElement('directory')
-            directory_elt['name'] = name
+            directory_elt = query_elt.addElement("directory")
+            directory_elt["name"] = name
         client.send(iq_result_elt)
 
     def _getFilesFromNodeCb(self, client, iq_elt, owner, node_path):
         """Main method to retrieve files/directories from a node_path"""
-        peer_jid = jid.JID(iq_elt[u'from'])
+        peer_jid = jid.JID(iq_elt[u"from"])
         try:
             node, path = ShareNode.find(client, node_path, peer_jid)
         except (exceptions.PermissionError, exceptions.NotFound):
             return self._iqError(client, iq_elt)
         except exceptions.DataError:
-            return self._iqError(client, iq_elt, condition='not-acceptable')
+            return self._iqError(client, iq_elt, condition="not-acceptable")
 
         node_type = node.type
-        peer_jid = jid.JID(iq_elt['from'])
-        iq_result_elt = xmlstream.toResponse(iq_elt, 'result')
-        query_elt = iq_result_elt.addElement((NS_FIS, 'query'))
-        query_elt[u'node'] = node_path
+        peer_jid = jid.JID(iq_elt["from"])
+        iq_result_elt = xmlstream.toResponse(iq_elt, "result")
+        query_elt = iq_result_elt.addElement((NS_FIS, "query"))
+        query_elt[u"node"] = node_path
 
         # we now fill query_elt according to node_type
         if node_type == TYPE_PATH:
-            # it's a physical path
+            #  it's a physical path
             self._pathNodeHandler(client, iq_elt, query_elt, node, path)
         elif node_type == TYPE_VIRTUAL:
             assert not path
             self._virtualNodeHandler(client, peer_jid, iq_elt, query_elt, node)
         else:
-            raise exceptions.InternalError(_(u'unknown node type: {type}').format(type=node_type))
+            raise exceptions.InternalError(
+                _(u"unknown node type: {type}").format(type=node_type)
+            )
 
         client.send(iq_result_elt)
 
     def onRequest(self, iq_elt, client):
-        return self._requestHandler(client, iq_elt, self._getRootNodesCb, self._getFilesFromNodeCb)
+        return self._requestHandler(
+            client, iq_elt, self._getRootNodesCb, self._getFilesFromNodeCb
+        )
 
     # Component
 
     @defer.inlineCallbacks
     def _compGetRootNodesCb(self, client, iq_elt, owner):
-        peer_jid = jid.JID(iq_elt['from'])
-        files_data = yield self.host.memory.getFiles(client, peer_jid=peer_jid, parent=u'',
-                                                     type_=C.FILE_TYPE_DIRECTORY, owner=owner)
-        iq_result_elt = xmlstream.toResponse(iq_elt, 'result')
-        query_elt = iq_result_elt.addElement((NS_FIS, 'query'))
+        peer_jid = jid.JID(iq_elt["from"])
+        files_data = yield self.host.memory.getFiles(
+            client,
+            peer_jid=peer_jid,
+            parent=u"",
+            type_=C.FILE_TYPE_DIRECTORY,
+            owner=owner,
+        )
+        iq_result_elt = xmlstream.toResponse(iq_elt, "result")
+        query_elt = iq_result_elt.addElement((NS_FIS, "query"))
         for file_data in files_data:
-            name = file_data[u'name']
-            directory_elt = query_elt.addElement(u'directory')
-            directory_elt[u'name'] = name
+            name = file_data[u"name"]
+            directory_elt = query_elt.addElement(u"directory")
+            directory_elt[u"name"] = name
         client.send(iq_result_elt)
 
     @defer.inlineCallbacks
@@ -479,44 +549,53 @@
         result stanza is then built and sent to requestor
         @trigger XEP-0329_compGetFilesFromNode(client, iq_elt, owner, node_path, files_data): can be used to add data/elements
         """
-        peer_jid = jid.JID(iq_elt['from'])
+        peer_jid = jid.JID(iq_elt["from"])
         try:
-            files_data = yield self.host.memory.getFiles(client, peer_jid=peer_jid, path=node_path, owner=owner)
+            files_data = yield self.host.memory.getFiles(
+                client, peer_jid=peer_jid, path=node_path, owner=owner
+            )
         except exceptions.NotFound:
             self._iqError(client, iq_elt)
             return
-        iq_result_elt = xmlstream.toResponse(iq_elt, 'result')
-        query_elt = iq_result_elt.addElement((NS_FIS, 'query'))
-        query_elt[u'node'] = node_path
-        if not self.host.trigger.point(u'XEP-0329_compGetFilesFromNode', client, iq_elt, owner, node_path, files_data):
+        iq_result_elt = xmlstream.toResponse(iq_elt, "result")
+        query_elt = iq_result_elt.addElement((NS_FIS, "query"))
+        query_elt[u"node"] = node_path
+        if not self.host.trigger.point(
+            u"XEP-0329_compGetFilesFromNode", client, iq_elt, owner, node_path, files_data
+        ):
             return
         for file_data in files_data:
-            file_elt = self._jf.buildFileElementFromDict(file_data,
-                                                         modified=file_data.get(u'modified', file_data[u'created']))
+            file_elt = self._jf.buildFileElementFromDict(
+                file_data, modified=file_data.get(u"modified", file_data[u"created"])
+            )
             query_elt.addChild(file_elt)
         client.send(iq_result_elt)
 
     def onComponentRequest(self, iq_elt, client):
-        return self._requestHandler(client, iq_elt, self._compGetRootNodesCb, self._compGetFilesFromNodeCb)
+        return self._requestHandler(
+            client, iq_elt, self._compGetRootNodesCb, self._compGetFilesFromNodeCb
+        )
 
     def _parseResult(self, iq_elt):
-        query_elt = next(iq_elt.elements(NS_FIS, 'query'))
+        query_elt = next(iq_elt.elements(NS_FIS, "query"))
         files = []
 
         for elt in query_elt.elements():
-            if elt.name == 'file':
+            if elt.name == "file":
                 # we have a file
                 try:
                     file_data = self._jf.parseFileElement(elt)
                 except exceptions.DataError:
                     continue
-                file_data[u'type'] = C.FILE_TYPE_FILE
-            elif elt.name == 'directory' and elt.uri == NS_FIS:
+                file_data[u"type"] = C.FILE_TYPE_FILE
+            elif elt.name == "directory" and elt.uri == NS_FIS:
                 # we have a directory
 
-                file_data = {'name': elt['name'], 'type': C.FILE_TYPE_DIRECTORY}
+                file_data = {"name": elt["name"], "type": C.FILE_TYPE_DIRECTORY}
             else:
-                log.warning(_(u"unexpected element, ignoring: {elt}").format(elt=elt.toXml()))
+                log.warning(
+                    _(u"unexpected element, ignoring: {elt}").format(elt=elt.toXml())
+                )
                 continue
             files.append(file_data)
         return files
@@ -526,7 +605,9 @@
     def _serializeData(self, files_data):
         for file_data in files_data:
             for key, value in file_data.iteritems():
-                file_data[key] = json.dumps(value) if key in ('extra',) else unicode(value)
+                file_data[key] = (
+                    json.dumps(value) if key in ("extra",) else unicode(value)
+                )
         return files_data
 
     def _listFiles(self, target_jid, path, extra, profile):
@@ -545,11 +626,11 @@
         @param extra(dict, None): extra data
         @return list(dict): shared files
         """
-        iq_elt = client.IQ('get')
-        iq_elt['to'] = target_jid.full()
-        query_elt = iq_elt.addElement((NS_FIS, 'query'))
+        iq_elt = client.IQ("get")
+        iq_elt["to"] = target_jid.full()
+        query_elt = iq_elt.addElement((NS_FIS, "query"))
         if path:
-            query_elt['node'] = path
+            query_elt["node"] = path
         d = iq_elt.send()
         d.addCallback(self._parseResult)
         return d
@@ -563,7 +644,7 @@
 
     def _sharePath(self, name, path, access, profile):
         client = self.host.getClient(profile)
-        access= json.loads(access)
+        access = json.loads(access)
         return self.sharePath(client, name or None, path, access)
 
     def sharePath(self, client, name, path, access):
@@ -571,31 +652,36 @@
             raise exceptions.ClientTypeError
         if not os.path.exists(path):
             raise ValueError(_(u"This path doesn't exist!"))
-        if not path or not path.strip(u' /'):
+        if not path or not path.strip(u" /"):
             raise ValueError(_(u"A path need to be specified"))
         if not isinstance(access, dict):
-            raise ValueError(_(u'access must be a dict'))
+            raise ValueError(_(u"access must be a dict"))
 
         node = client._XEP_0329_root_node
         node_type = TYPE_PATH
         if os.path.isfile(path):
-            # we have a single file, the workflow is diferrent as we store all single files in the same dir
+            #  we have a single file, the workflow is diferrent as we store all single files in the same dir
             node = node.getOrCreate(SINGLE_FILES_DIR)
 
         if not name:
-            name = os.path.basename(path.rstrip(u' /'))
+            name = os.path.basename(path.rstrip(u" /"))
             if not name:
                 raise exceptions.InternalError(_(u"Can't find a proper name"))
 
         if name in node or name == SINGLE_FILES_DIR:
             idx = 1
-            new_name = name + '_' + unicode(idx)
+            new_name = name + "_" + unicode(idx)
             while new_name in node:
                 idx += 1
-                new_name = name + '_' + unicode(idx)
+                new_name = name + "_" + unicode(idx)
             name = new_name
-            log.info(_(u"A directory with this name is already shared, renamed to {new_name} [{profile}]".format(
-                new_name=new_name, profile=client.profile)))
+            log.info(
+                _(
+                    u"A directory with this name is already shared, renamed to {new_name} [{profile}]".format(
+                        new_name=new_name, profile=client.profile
+                    )
+                )
+            )
 
         ShareNode(name=name, parent=node, type_=node_type, access=access, path=path)
         self.host.bridge.FISSharedPathNew(path, name, client.profile)
@@ -621,12 +707,16 @@
 
     def connectionInitialized(self):
         if self.parent.is_component:
-            self.xmlstream.addObserver(IQ_FIS_REQUEST, self.plugin_parent.onComponentRequest, client=self.parent)
+            self.xmlstream.addObserver(
+                IQ_FIS_REQUEST, self.plugin_parent.onComponentRequest, client=self.parent
+            )
         else:
-            self.xmlstream.addObserver(IQ_FIS_REQUEST, self.plugin_parent.onRequest, client=self.parent)
+            self.xmlstream.addObserver(
+                IQ_FIS_REQUEST, self.plugin_parent.onRequest, client=self.parent
+            )
 
-    def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
+    def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
         return [disco.DiscoFeature(NS_FIS)]
 
-    def getDiscoItems(self, requestor, target, nodeIdentifier=''):
+    def getDiscoItems(self, requestor, target, nodeIdentifier=""):
         return []
--- a/sat/plugins/plugin_xep_0334.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_xep_0334.py	Wed Jun 27 20:14:46 2018 +0200
@@ -20,12 +20,14 @@
 
 from sat.core.i18n import _, D_
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from sat.core.constants import Const as C
 
 from sat.tools.common import data_format
 
 from wokkel import disco, iwokkel
+
 try:
     from twisted.words.protocols.xmlstream import XMPPHandler
 except ImportError:
@@ -42,21 +44,24 @@
     C.PI_MAIN: "XEP_0334",
     C.PI_HANDLER: u"yes",
     C.PI_DESCRIPTION: D_(u"""Implementation of Message Processing Hints"""),
-    C.PI_USAGE: dedent(D_(u"""\
+    C.PI_USAGE: dedent(
+        D_(
+            u"""\
              Frontends can use HINT_* constants in mess_data['extra'] in a serialized 'hints' dict.
              Internal plugins can use directly addHint([HINT_* constant]).
-             Will set mess_data['extra']['history'] to 'skipped' when no store is requested and message is not saved in history."""))
-
+             Will set mess_data['extra']['history'] to 'skipped' when no store is requested and message is not saved in history."""
+        )
+    ),
 }
 
-NS_HINTS = u'urn:xmpp:hints'
+NS_HINTS = u"urn:xmpp:hints"
 
 
 class XEP_0334(object):
-    HINT_NO_PERMANENT_STORE = u'no-permanent-store'
-    HINT_NO_STORE = u'no-store'
-    HINT_NO_COPY = u'no-copy'
-    HINT_STORE = u'store'
+    HINT_NO_PERMANENT_STORE = u"no-permanent-store"
+    HINT_NO_STORE = u"no-store"
+    HINT_NO_COPY = u"no-copy"
+    HINT_STORE = u"store"
     HINTS = (HINT_NO_PERMANENT_STORE, HINT_NO_STORE, HINT_NO_COPY, HINT_STORE)
 
     def __init__(self, host):
@@ -69,38 +74,45 @@
         return XEP_0334_handler()
 
     def addHint(self, mess_data, hint):
-        if hint == self.HINT_NO_COPY and not mess_data['to'].resource:
-            log.error(u"{hint} can only be used with full jids! Ignoring it.".format(hint=hint))
+        if hint == self.HINT_NO_COPY and not mess_data["to"].resource:
+            log.error(
+                u"{hint} can only be used with full jids! Ignoring it.".format(hint=hint)
+            )
             return
-        hints = mess_data.setdefault('hints', set())
+        hints = mess_data.setdefault("hints", set())
         if hint in self.HINTS:
             hints.add(hint)
         else:
             log.error(u"Unknown hint: {}".format(hint))
 
     def _sendPostXmlTreatment(self, mess_data):
-        if 'hints' in mess_data:
-            for hint in mess_data['hints']:
-                mess_data[u'xml'].addElement((NS_HINTS, hint))
+        if "hints" in mess_data:
+            for hint in mess_data["hints"]:
+                mess_data[u"xml"].addElement((NS_HINTS, hint))
         return mess_data
 
-    def sendMessageTrigger(self, client, mess_data, pre_xml_treatments, post_xml_treatments):
+    def sendMessageTrigger(
+        self, client, mess_data, pre_xml_treatments, post_xml_treatments
+    ):
         """Add the hints element to the message to be sent"""
-        if u'hints' in mess_data[u'extra']:
-            for hint in data_format.dict2iter(u'hints', mess_data[u'extra'], pop=True):
+        if u"hints" in mess_data[u"extra"]:
+            for hint in data_format.dict2iter(u"hints", mess_data[u"extra"], pop=True):
                 self.addHint(hint)
 
         post_xml_treatments.addCallback(self._sendPostXmlTreatment)
         return True
 
     def _receivedSkipHistory(self, mess_data):
-        mess_data[u'history'] = C.HISTORY_SKIP
+        mess_data[u"history"] = C.HISTORY_SKIP
         return mess_data
 
     def messageReceivedTrigger(self, client, message_elt, post_treat):
         """Check for hints in the received message"""
         for elt in message_elt.elements():
-            if elt.uri == NS_HINTS and elt.name in (self.HINT_NO_PERMANENT_STORE, self.HINT_NO_STORE):
+            if elt.uri == NS_HINTS and elt.name in (
+                self.HINT_NO_PERMANENT_STORE,
+                self.HINT_NO_STORE,
+            ):
                 log.debug(u"history will be skipped for this message, as requested")
                 post_treat.addCallback(self._receivedSkipHistory)
                 break
@@ -110,8 +122,8 @@
 class XEP_0334_handler(XMPPHandler):
     implements(iwokkel.IDisco)
 
-    def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
+    def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
         return [disco.DiscoFeature(NS_HINTS)]
 
-    def getDiscoItems(self, requestor, target, nodeIdentifier=''):
+    def getDiscoItems(self, requestor, target, nodeIdentifier=""):
         return []
--- a/sat/plugins/plugin_xep_0363.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/plugins/plugin_xep_0363.py	Wed Jun 27 20:14:46 2018 +0200
@@ -20,6 +20,7 @@
 from sat.core.i18n import _
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from sat.core import exceptions
 from wokkel import disco, iwokkel
@@ -49,18 +50,17 @@
     C.PI_DEPENDENCIES: ["FILE", "UPLOAD"],
     C.PI_MAIN: "XEP_0363",
     C.PI_HANDLER: "yes",
-    C.PI_DESCRIPTION: _("""Implementation of HTTP File Upload""")
+    C.PI_DESCRIPTION: _("""Implementation of HTTP File Upload"""),
 }
 
-NS_HTTP_UPLOAD = 'urn:xmpp:http:upload'
+NS_HTTP_UPLOAD = "urn:xmpp:http:upload"
 
 
-Slot = namedtuple('Slot', ['put', 'get'])
+Slot = namedtuple("Slot", ["put", "get"])
 
 
 @implementer(IOpenSSLClientConnectionCreator)
 class NoCheckConnectionCreator(object):
-
     def __init__(self, hostname, ctx):
         self._ctx = ctx
 
@@ -80,53 +80,91 @@
     """
 
     def creatorForNetloc(self, hostname, port):
-        log.warning(u"TLS check disabled for {host} on port {port}".format(host=hostname, port=port))
+        log.warning(
+            u"TLS check disabled for {host} on port {port}".format(
+                host=hostname, port=port
+            )
+        )
         certificateOptions = ssl.CertificateOptions(trustRoot=None)
         return NoCheckConnectionCreator(hostname, certificateOptions.getContext())
 
 
 class XEP_0363(object):
-
     def __init__(self, host):
         log.info(_("plugin HTTP File Upload initialization"))
         self.host = host
-        host.bridge.addMethod("fileHTTPUpload", ".plugin", in_sign='sssbs', out_sign='', method=self._fileHTTPUpload)
-        host.bridge.addMethod("fileHTTPUploadGetSlot", ".plugin", in_sign='sisss', out_sign='(ss)', method=self._getSlot, async=True)
-        host.plugins['UPLOAD'].register(u"HTTP Upload", self.getHTTPUploadEntity, self.fileHTTPUpload)
+        host.bridge.addMethod(
+            "fileHTTPUpload",
+            ".plugin",
+            in_sign="sssbs",
+            out_sign="",
+            method=self._fileHTTPUpload,
+        )
+        host.bridge.addMethod(
+            "fileHTTPUploadGetSlot",
+            ".plugin",
+            in_sign="sisss",
+            out_sign="(ss)",
+            method=self._getSlot,
+            async=True,
+        )
+        host.plugins["UPLOAD"].register(
+            u"HTTP Upload", self.getHTTPUploadEntity, self.fileHTTPUpload
+        )
 
     def getHandler(self, client):
         return XEP_0363_handler()
 
     @defer.inlineCallbacks
     def getHTTPUploadEntity(self, upload_jid=None, profile=C.PROF_KEY_NONE):
-         """Get HTTP upload capable entity
+        """Get HTTP upload capable entity
 
          upload_jid is checked, then its components
          @param upload_jid(None, jid.JID): entity to check
          @return(D(jid.JID)): first HTTP upload capable entity
          @raise exceptions.NotFound: no entity found
          """
-         client = self.host.getClient(profile)
-         try:
-             entity = client.http_upload_service
-         except AttributeError:
-             found_entities = yield self.host.findFeaturesSet(client, (NS_HTTP_UPLOAD,))
-             try:
-                 entity = client.http_upload_service = iter(found_entities).next()
-             except StopIteration:
-                 entity = client.http_upload_service = None
+        client = self.host.getClient(profile)
+        try:
+            entity = client.http_upload_service
+        except AttributeError:
+            found_entities = yield self.host.findFeaturesSet(client, (NS_HTTP_UPLOAD,))
+            try:
+                entity = client.http_upload_service = iter(found_entities).next()
+            except StopIteration:
+                entity = client.http_upload_service = None
+
+        if entity is None:
+            raise failure.Failure(exceptions.NotFound(u"No HTTP upload entity found"))
+
+        defer.returnValue(entity)
 
-         if entity is None:
-             raise failure.Failure(exceptions.NotFound(u'No HTTP upload entity found'))
-
-         defer.returnValue(entity)
-
-    def _fileHTTPUpload(self, filepath, filename='', upload_jid='', ignore_tls_errors=False, profile=C.PROF_KEY_NONE):
+    def _fileHTTPUpload(
+        self,
+        filepath,
+        filename="",
+        upload_jid="",
+        ignore_tls_errors=False,
+        profile=C.PROF_KEY_NONE,
+    ):
         assert os.path.isabs(filepath) and os.path.isfile(filepath)
-        progress_id_d, dummy = self.fileHTTPUpload(filepath, filename or None, jid.JID(upload_jid) if upload_jid else None, {'ignore_tls_errors': ignore_tls_errors}, profile)
+        progress_id_d, dummy = self.fileHTTPUpload(
+            filepath,
+            filename or None,
+            jid.JID(upload_jid) if upload_jid else None,
+            {"ignore_tls_errors": ignore_tls_errors},
+            profile,
+        )
         return progress_id_d
 
-    def fileHTTPUpload(self, filepath, filename=None, upload_jid=None, options=None, profile=C.PROF_KEY_NONE):
+    def fileHTTPUpload(
+        self,
+        filepath,
+        filename=None,
+        upload_jid=None,
+        options=None,
+        profile=C.PROF_KEY_NONE,
+    ):
         """upload a file through HTTP
 
         @param filepath(str): absolute path of the file
@@ -141,14 +179,20 @@
         """
         if options is None:
             options = {}
-        ignore_tls_errors = options.get('ignore_tls_errors', False)
+        ignore_tls_errors = options.get("ignore_tls_errors", False)
         client = self.host.getClient(profile)
         filename = filename or os.path.basename(filepath)
         size = os.path.getsize(filepath)
         progress_id_d = defer.Deferred()
         download_d = defer.Deferred()
         d = self.getSlot(client, filename, size, upload_jid=upload_jid)
-        d.addCallbacks(self._getSlotCb, self._getSlotEb, (client, progress_id_d, download_d, filepath, size, ignore_tls_errors), None, (client, progress_id_d, download_d))
+        d.addCallbacks(
+            self._getSlotCb,
+            self._getSlotEb,
+            (client, progress_id_d, download_d, filepath, size, ignore_tls_errors),
+            None,
+            (client, progress_id_d, download_d),
+        )
         return progress_id_d, download_d
 
     def _getSlotEb(self, fail, client, progress_id_d, download_d):
@@ -157,7 +201,9 @@
         progress_id_d.errback(fail)
         download_d.errback(fail)
 
-    def _getSlotCb(self, slot, client, progress_id_d, download_d, path, size, ignore_tls_errors=False):
+    def _getSlotCb(
+        self, slot, client, progress_id_d, download_d, path, size, ignore_tls_errors=False
+    ):
         """Called when slot is received, try to do the upload
 
         @param slot(Slot): slot instance with the get and put urls
@@ -169,15 +215,28 @@
         @return (tuple
         """
         log.debug(u"Got upload slot: {}".format(slot))
-        sat_file = self.host.plugins['FILE'].File(self.host, client, path, size=size, auto_end_signals=False)
+        sat_file = self.host.plugins["FILE"].File(
+            self.host, client, path, size=size, auto_end_signals=False
+        )
         progress_id_d.callback(sat_file.uid)
         file_producer = http_client.FileBodyProducer(sat_file)
         if ignore_tls_errors:
             agent = http_client.Agent(reactor, NoCheckContextFactory())
         else:
             agent = http_client.Agent(reactor)
-        d = agent.request('PUT', slot.put.encode('utf-8'), http_headers.Headers({'User-Agent': [C.APP_NAME.encode('utf-8')]}), file_producer)
-        d.addCallbacks(self._uploadCb, self._uploadEb, (sat_file, slot, download_d), None, (sat_file, download_d))
+        d = agent.request(
+            "PUT",
+            slot.put.encode("utf-8"),
+            http_headers.Headers({"User-Agent": [C.APP_NAME.encode("utf-8")]}),
+            file_producer,
+        )
+        d.addCallbacks(
+            self._uploadCb,
+            self._uploadEb,
+            (sat_file, slot, download_d),
+            None,
+            (sat_file, download_d),
+        )
         return d
 
     def _uploadCb(self, dummy, sat_file, slot, download_d):
@@ -188,7 +247,7 @@
         @param slot(Slot): put/get urls
         """
         log.info(u"HTTP upload finished")
-        sat_file.progressFinished({'url': slot.get})
+        sat_file.progressFinished({"url": slot.get})
         download_d.callback(slot.get)
 
     def _uploadEb(self, fail, sat_file, download_d):
@@ -216,15 +275,17 @@
         @param iq_elt(domish.Element): <IQ/> result as specified in XEP-0363
         """
         try:
-            slot_elt = iq_elt.elements(NS_HTTP_UPLOAD, 'slot').next()
-            put_url = unicode(slot_elt.elements(NS_HTTP_UPLOAD, 'put').next())
-            get_url = unicode(slot_elt.elements(NS_HTTP_UPLOAD, 'get').next())
+            slot_elt = iq_elt.elements(NS_HTTP_UPLOAD, "slot").next()
+            put_url = unicode(slot_elt.elements(NS_HTTP_UPLOAD, "put").next())
+            get_url = unicode(slot_elt.elements(NS_HTTP_UPLOAD, "get").next())
         except StopIteration:
             raise exceptions.DataError(u"Incorrect stanza received from server")
         slot = Slot(put=put_url, get=get_url)
         return slot
 
-    def _getSlot(self, filename, size, content_type, upload_jid, profile_key=C.PROF_KEY_NONE):
+    def _getSlot(
+        self, filename, size, content_type, upload_jid, profile_key=C.PROF_KEY_NONE
+    ):
         """Get a upload slot
 
         This method can be used when uploading is done by the frontend
@@ -234,9 +295,11 @@
         @param content_type(unicode, None): MIME type of the content
             empty string or None to guess automatically
         """
-        filename = filename.replace('/', '_')
+        filename = filename.replace("/", "_")
         client = self.host.getClient(profile_key)
-        return  self.getSlot(client, filename, size, content_type or None, upload_jid or None)
+        return self.getSlot(
+            client, filename, size, content_type or None, upload_jid or None
+        )
 
     def getSlot(self, client, filename, size, content_type=None, upload_jid=None):
         """Get a slot (i.e. download/upload links)
@@ -261,19 +324,25 @@
                 upload_jid = client.http_upload_service
             except AttributeError:
                 d = self.getHTTPUploadEntity(profile=client.profile)
-                d.addCallback(lambda found_entity: self.getSlot(client, filename, size, content_type, found_entity))
+                d.addCallback(
+                    lambda found_entity: self.getSlot(
+                        client, filename, size, content_type, found_entity
+                    )
+                )
                 return d
             else:
                 if upload_jid is None:
-                    raise failure.Failure(exceptions.NotFound(u'No HTTP upload entity found'))
+                    raise failure.Failure(
+                        exceptions.NotFound(u"No HTTP upload entity found")
+                    )
 
-        iq_elt = client.IQ('get')
-        iq_elt['to'] = upload_jid.full()
-        request_elt = iq_elt.addElement((NS_HTTP_UPLOAD, 'request'))
-        request_elt.addElement('filename', content=filename)
-        request_elt.addElement('size', content=unicode(size))
+        iq_elt = client.IQ("get")
+        iq_elt["to"] = upload_jid.full()
+        request_elt = iq_elt.addElement((NS_HTTP_UPLOAD, "request"))
+        request_elt.addElement("filename", content=filename)
+        request_elt.addElement("size", content=unicode(size))
         if content_type is not None:
-            request_elt.addElement('content-type', content=content_type)
+            request_elt.addElement("content-type", content=content_type)
 
         d = iq_elt.send()
         d.addCallback(self._gotSlot, client)
@@ -284,8 +353,8 @@
 class XEP_0363_handler(XMPPHandler):
     implements(iwokkel.IDisco)
 
-    def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
+    def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
         return [disco.DiscoFeature(NS_HTTP_UPLOAD)]
 
-    def getDiscoItems(self, requestor, target, nodeIdentifier=''):
+    def getDiscoItems(self, requestor, target, nodeIdentifier=""):
         return []
--- a/sat/stdui/ui_contact_list.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/stdui/ui_contact_list.py	Wed Jun 27 20:14:46 2018 +0200
@@ -31,17 +31,34 @@
         self.host = host
         self.__add_id = host.registerCallback(self._addContact, with_data=True)
         self.__update_id = host.registerCallback(self._updateContact, with_data=True)
-        self.__confirm_delete_id = host.registerCallback(self._getConfirmRemoveXMLUI, with_data=True)
+        self.__confirm_delete_id = host.registerCallback(
+            self._getConfirmRemoveXMLUI, with_data=True
+        )
 
-        host.importMenu((D_("Contacts"), D_("Add contact")), self._getAddDialogXMLUI, security_limit=2, help_string=D_("Add contact"))
-        host.importMenu((D_("Contacts"), D_("Update contact")), self._getUpdateDialogXMLUI, security_limit=2, help_string=D_("Update contact"))
-        host.importMenu((D_("Contacts"), D_("Remove contact")), self._getRemoveDialogXMLUI, security_limit=2, help_string=D_("Remove contact"))
+        host.importMenu(
+            (D_("Contacts"), D_("Add contact")),
+            self._getAddDialogXMLUI,
+            security_limit=2,
+            help_string=D_("Add contact"),
+        )
+        host.importMenu(
+            (D_("Contacts"), D_("Update contact")),
+            self._getUpdateDialogXMLUI,
+            security_limit=2,
+            help_string=D_("Update contact"),
+        )
+        host.importMenu(
+            (D_("Contacts"), D_("Remove contact")),
+            self._getRemoveDialogXMLUI,
+            security_limit=2,
+            help_string=D_("Remove contact"),
+        )
 
         # FIXME: a plugin should not be used here, and current profile's jid host would be better than installation wise host
-        if 'MISC-ACCOUNT' in self.host.plugins:
-            self.default_host = self.host.plugins['MISC-ACCOUNT'].getNewAccountDomain()
+        if "MISC-ACCOUNT" in self.host.plugins:
+            self.default_host = self.host.plugins["MISC-ACCOUNT"].getNewAccountDomain()
         else:
-            self.default_host = 'example.net'
+            self.default_host = "example.net"
 
     def getContacts(self, profile):
         """Return a sorted list of the contacts for that profile
@@ -96,11 +113,11 @@
         """
         elts = []
         for key in data:
-            key_elt = Element('jid')
-            key_elt.setAttribute('name', key)
+            key_elt = Element("jid")
+            key_elt.setAttribute("name", key)
             for value in data[key]:
-                value_elt = Element('group')
-                value_elt.setAttribute('name', value)
+                value_elt = Element("group")
+                value_elt.setAttribute("name", value)
                 key_elt.childNodes.append(value_elt)
             elts.append(key_elt)
         return elts
@@ -116,44 +133,50 @@
         @param profile: %(doc_profile)s
         @return dict
         """
-        form_ui = xml_tools.XMLUI("form", title=options['title'], submit_id=options['id'])
-        if 'message' in data:
-            form_ui.addText(data['message'])
-            form_ui.addDivider('dash')
+        form_ui = xml_tools.XMLUI("form", title=options["title"], submit_id=options["id"])
+        if "message" in data:
+            form_ui.addText(data["message"])
+            form_ui.addDivider("dash")
 
-        form_ui.addText(options['contact_text'])
-        if options['id'] == self.__add_id:
-            contact = data.get(xml_tools.formEscape('contact_jid'), '@%s' % self.default_host)
-            form_ui.addString('contact_jid', value=contact)
-        elif options['id'] == self.__update_id:
+        form_ui.addText(options["contact_text"])
+        if options["id"] == self.__add_id:
+            contact = data.get(
+                xml_tools.formEscape("contact_jid"), "@%s" % self.default_host
+            )
+            form_ui.addString("contact_jid", value=contact)
+        elif options["id"] == self.__update_id:
             contacts = self.getContacts(profile)
-            list_ = form_ui.addList('contact_jid', options=contacts, selected=contacts[0])
+            list_ = form_ui.addList("contact_jid", options=contacts, selected=contacts[0])
             elts = self._data2elts(self.getGroupsOfAllContacts(profile))
-            list_.setInternalCallback('groups_of_contact', fields=['contact_jid', 'groups_list'], data_elts=elts)
+            list_.setInternalCallback(
+                "groups_of_contact", fields=["contact_jid", "groups_list"], data_elts=elts
+            )
 
-        form_ui.addDivider('blank')
+        form_ui.addDivider("blank")
 
         form_ui.addText(_("Select in which groups your contact is:"))
         selected_groups = []
-        if 'selected_groups' in data:
-            selected_groups = data['selected_groups']
-        elif options['id'] == self.__update_id:
+        if "selected_groups" in data:
+            selected_groups = data["selected_groups"]
+        elif options["id"] == self.__update_id:
             try:
                 selected_groups = self.getGroupsOfContact(contacts[0], profile)
             except IndexError:
                 pass
         groups = self.getGroups(selected_groups, profile)
-        form_ui.addList('groups_list', options=groups, selected=selected_groups, styles=['multi'])
+        form_ui.addList(
+            "groups_list", options=groups, selected=selected_groups, styles=["multi"]
+        )
 
-        adv_list = form_ui.changeContainer("advanced_list", columns=3, selectable='no')
+        adv_list = form_ui.changeContainer("advanced_list", columns=3, selectable="no")
         form_ui.addLabel(D_("Add group"))
         form_ui.addString("add_group")
-        button = form_ui.addButton('', value=D_('Add'))
-        button.setInternalCallback('move', fields=['add_group', 'groups_list'])
+        button = form_ui.addButton("", value=D_("Add"))
+        button.setInternalCallback("move", fields=["add_group", "groups_list"])
         adv_list.end()
 
-        form_ui.addDivider('blank')
-        return {'xmlui': form_ui.toXml()}
+        form_ui.addDivider("blank")
+        return {"xmlui": form_ui.toXml()}
 
     def _getAddDialogXMLUI(self, data, profile):
         """Get the dialog for adding contact
@@ -162,10 +185,11 @@
         @param profile: %(doc_profile)s
         @return dict
         """
-        options = {'id': self.__add_id,
-                   'title': D_('Add contact'),
-                   'contact_text': D_("New contact identifier (JID):"),
-                   }
+        options = {
+            "id": self.__add_id,
+            "title": D_("Add contact"),
+            "contact_text": D_("New contact identifier (JID):"),
+        }
         return self.getDialogXMLUI(options, {}, profile)
 
     def _getUpdateDialogXMLUI(self, data, profile):
@@ -176,14 +200,15 @@
         @return dict
         """
         if not self.getContacts(profile):
-            _dialog = xml_tools.XMLUI('popup', title=D_('Nothing to update'))
-            _dialog.addText(_('Your contact list is empty.'))
-            return {'xmlui': _dialog.toXml()}
+            _dialog = xml_tools.XMLUI("popup", title=D_("Nothing to update"))
+            _dialog.addText(_("Your contact list is empty."))
+            return {"xmlui": _dialog.toXml()}
 
-        options = {'id': self.__update_id,
-                   'title': D_('Update contact'),
-                   'contact_text': D_("Which contact do you want to update?"),
-                   }
+        options = {
+            "id": self.__update_id,
+            "title": D_("Update contact"),
+            "contact_text": D_("Which contact do you want to update?"),
+        }
         return self.getDialogXMLUI(options, {}, profile)
 
     def _getRemoveDialogXMLUI(self, data, profile):
@@ -194,13 +219,17 @@
         @return dict
         """
         if not self.getContacts(profile):
-            _dialog = xml_tools.XMLUI('popup', title=D_('Nothing to delete'))
-            _dialog.addText(_('Your contact list is empty.'))
-            return {'xmlui': _dialog.toXml()}
+            _dialog = xml_tools.XMLUI("popup", title=D_("Nothing to delete"))
+            _dialog.addText(_("Your contact list is empty."))
+            return {"xmlui": _dialog.toXml()}
 
-        form_ui = xml_tools.XMLUI("form", title=D_('Who do you want to remove from your contacts?'), submit_id=self.__confirm_delete_id)
-        form_ui.addList('contact_jid', options=self.getContacts(profile))
-        return {'xmlui': form_ui.toXml()}
+        form_ui = xml_tools.XMLUI(
+            "form",
+            title=D_("Who do you want to remove from your contacts?"),
+            submit_id=self.__confirm_delete_id,
+        )
+        form_ui.addList("contact_jid", options=self.getContacts(profile))
+        return {"xmlui": form_ui.toXml()}
 
     def _getConfirmRemoveXMLUI(self, data, profile):
         """Get the confirmation dialog for removing contact
@@ -209,17 +238,21 @@
         @param profile: %(doc_profile)s
         @return dict
         """
-        if C.bool(data.get('cancelled', 'false')):
+        if C.bool(data.get("cancelled", "false")):
             return {}
-        contact = data[xml_tools.formEscape('contact_jid')]
+        contact = data[xml_tools.formEscape("contact_jid")]
+
         def delete_cb(data, profile):
-            if not C.bool(data.get('cancelled', 'false')):
+            if not C.bool(data.get("cancelled", "false")):
                 self._deleteContact(jid.JID(contact), profile)
             return {}
+
         delete_id = self.host.registerCallback(delete_cb, with_data=True, one_shot=True)
         form_ui = xml_tools.XMLUI("form", title=D_("Delete contact"), submit_id=delete_id)
-        form_ui.addText(D_("Are you sure you want to remove %s from your contact list?") % contact)
-        return {'xmlui': form_ui.toXml()}
+        form_ui.addText(
+            D_("Are you sure you want to remove %s from your contact list?") % contact
+        )
+        return {"xmlui": form_ui.toXml()}
 
     def _addContact(self, data, profile):
         """Add the selected contact
@@ -228,18 +261,22 @@
         @param profile: %(doc_profile)s
         @return dict
         """
-        if C.bool(data.get('cancelled', 'false')):
+        if C.bool(data.get("cancelled", "false")):
             return {}
-        contact_jid_s = data[xml_tools.formEscape('contact_jid')]
+        contact_jid_s = data[xml_tools.formEscape("contact_jid")]
         try:
             contact_jid = jid.JID(contact_jid_s)
         except (RuntimeError, jid.InvalidFormat, AttributeError):
             # TODO: replace '\t' by a constant (see tools.xmlui.XMLUI.onFormSubmitted)
-            data['selected_groups'] = data[xml_tools.formEscape('groups_list')].split('\t')
-            options = {'id': self.__add_id,
-                       'title': D_('Add contact'),
-                       'contact_text': D_('Please enter a valid JID (like "contact@%s"):') % self.default_host,
-                       }
+            data["selected_groups"] = data[xml_tools.formEscape("groups_list")].split(
+                "\t"
+            )
+            options = {
+                "id": self.__add_id,
+                "title": D_("Add contact"),
+                "contact_text": D_('Please enter a valid JID (like "contact@%s"):')
+                % self.default_host,
+            }
             return self.getDialogXMLUI(options, data, profile)
         self.host.addContact(contact_jid, profile_key=profile)
         return self._updateContact(data, profile)  # after adding, updating
@@ -251,12 +288,12 @@
         @param profile: %(doc_profile)s
         @return dict
         """
-        if C.bool(data.get('cancelled', 'false')):
+        if C.bool(data.get("cancelled", "false")):
             return {}
-        contact_jid = jid.JID(data[xml_tools.formEscape('contact_jid')])
+        contact_jid = jid.JID(data[xml_tools.formEscape("contact_jid")])
         # TODO: replace '\t' by a constant (see tools.xmlui.XMLUI.onFormSubmitted)
-        groups = data[xml_tools.formEscape('groups_list')].split('\t')
-        self.host.updateContact(contact_jid, name='', groups=groups, profile_key=profile)
+        groups = data[xml_tools.formEscape("groups_list")].split("\t")
+        self.host.updateContact(contact_jid, name="", groups=groups, profile_key=profile)
         return {}
 
     def _deleteContact(self, contact_jid, profile):
--- a/sat/stdui/ui_profile_manager.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/stdui/ui_profile_manager.py	Wed Jun 27 20:14:46 2018 +0200
@@ -21,6 +21,7 @@
 from sat.core.i18n import D_
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from sat.core import exceptions
 from sat.tools import xml_tools
@@ -35,9 +36,15 @@
         self.host = host
         self.profile_ciphers = {}
         self._sessions = ProfileSessions()
-        host.registerCallback(self._authenticateProfile, force_id=C.AUTHENTICATE_PROFILE_ID, with_data=True)
-        host.registerCallback(self._changeXMPPPassword, force_id=C.CHANGE_XMPP_PASSWD_ID, with_data=True)
-        self.__new_xmpp_passwd_id = host.registerCallback(self._changeXMPPPasswordCb, with_data=True)
+        host.registerCallback(
+            self._authenticateProfile, force_id=C.AUTHENTICATE_PROFILE_ID, with_data=True
+        )
+        host.registerCallback(
+            self._changeXMPPPassword, force_id=C.CHANGE_XMPP_PASSWD_ID, with_data=True
+        )
+        self.__new_xmpp_passwd_id = host.registerCallback(
+            self._changeXMPPPasswordCb, with_data=True
+        )
 
     def _startSessionEb(self, fail, first, profile):
         """Errback method for startSession during profile authentication
@@ -49,66 +56,93 @@
         """
         if first:
             # first call, we ask for the password
-            form_ui = xml_tools.XMLUI("form", title=D_('Profile password for {}').format(profile), submit_id='')
-            form_ui.addPassword('profile_password', value='')
+            form_ui = xml_tools.XMLUI(
+                "form", title=D_("Profile password for {}").format(profile), submit_id=""
+            )
+            form_ui.addPassword("profile_password", value="")
             d = xml_tools.deferredUI(self.host, form_ui, chained=True)
             d.addCallback(self._authenticateProfile, profile)
-            return {'xmlui': form_ui.toXml()}
+            return {"xmlui": form_ui.toXml()}
 
         assert profile is None
 
         if fail.check(exceptions.PasswordError):
-            dialog = xml_tools.XMLUI('popup', title=D_('Connection error'))
+            dialog = xml_tools.XMLUI("popup", title=D_("Connection error"))
             dialog.addText(D_("The provided profile password doesn't match."))
         else:
             log.error(u"Unexpected exceptions: {}".format(fail))
-            dialog = xml_tools.XMLUI('popup', title=D_('Internal error'))
+            dialog = xml_tools.XMLUI("popup", title=D_("Internal error"))
             dialog.addText(D_(u"Internal error: {}".format(fail)))
-        return {'xmlui': dialog.toXml(), 'validated': C.BOOL_FALSE}
+        return {"xmlui": dialog.toXml(), "validated": C.BOOL_FALSE}
 
     def _authenticateProfile(self, data, profile):
-        if C.bool(data.get('cancelled', 'false')):
+        if C.bool(data.get("cancelled", "false")):
             return {}
         if self.host.memory.isSessionStarted(profile):
-            return {'validated': C.BOOL_TRUE}
+            return {"validated": C.BOOL_TRUE}
         try:
-            password = data[xml_tools.formEscape('profile_password')]
+            password = data[xml_tools.formEscape("profile_password")]
         except KeyError:
             # first request, we try empty password
-            password = ''
+            password = ""
             first = True
             eb_profile = profile
         else:
             first = False
             eb_profile = None
         d = self.host.memory.startSession(password, profile)
-        d.addCallback(lambda dummy: {'validated': C.BOOL_TRUE})
+        d.addCallback(lambda dummy: {"validated": C.BOOL_TRUE})
         d.addErrback(self._startSessionEb, first, eb_profile)
         return d
 
     def _changeXMPPPassword(self, data, profile):
         session_data = self._sessions.profileGetUnique(profile)
         if not session_data:
-            server = self.host.memory.getParamA(C.FORCE_SERVER_PARAM, "Connection", profile_key=profile)
+            server = self.host.memory.getParamA(
+                C.FORCE_SERVER_PARAM, "Connection", profile_key=profile
+            )
             if not server:
-                server = jid.parse(self.host.memory.getParamA('JabberID', "Connection", profile_key=profile))[1]
-            session_id, session_data = self._sessions.newSession({'count': 0, 'server': server}, profile=profile)
-        if session_data['count'] > 2:  # 3 attempts with a new password after the initial try
+                server = jid.parse(
+                    self.host.memory.getParamA(
+                        "JabberID", "Connection", profile_key=profile
+                    )
+                )[1]
+            session_id, session_data = self._sessions.newSession(
+                {"count": 0, "server": server}, profile=profile
+            )
+        if (
+            session_data["count"] > 2
+        ):  # 3 attempts with a new password after the initial try
             self._sessions.profileDelUnique(profile)
-            _dialog = xml_tools.XMLUI('popup', title=D_('Connection error'))
-            _dialog.addText(D_("Can't connect to %s. Please check your connection details.") % session_data['server'])
-            return {'xmlui': _dialog.toXml()}
-        session_data['count'] += 1
-        counter = ' (%d)' % session_data['count'] if session_data['count'] > 1 else ''
-        title = D_('XMPP password for %(profile)s%(counter)s') % {'profile': profile, 'counter': counter}
-        form_ui = xml_tools.XMLUI("form", title=title, submit_id=self.__new_xmpp_passwd_id)
-        form_ui.addText(D_("Can't connect to %s. Please check your connection details or try with another password.") % session_data['server'])
-        form_ui.addPassword('xmpp_password', value='')
-        return {'xmlui': form_ui.toXml()}
+            _dialog = xml_tools.XMLUI("popup", title=D_("Connection error"))
+            _dialog.addText(
+                D_("Can't connect to %s. Please check your connection details.")
+                % session_data["server"]
+            )
+            return {"xmlui": _dialog.toXml()}
+        session_data["count"] += 1
+        counter = " (%d)" % session_data["count"] if session_data["count"] > 1 else ""
+        title = D_("XMPP password for %(profile)s%(counter)s") % {
+            "profile": profile,
+            "counter": counter,
+        }
+        form_ui = xml_tools.XMLUI(
+            "form", title=title, submit_id=self.__new_xmpp_passwd_id
+        )
+        form_ui.addText(
+            D_(
+                "Can't connect to %s. Please check your connection details or try with another password."
+            )
+            % session_data["server"]
+        )
+        form_ui.addPassword("xmpp_password", value="")
+        return {"xmlui": form_ui.toXml()}
 
     def _changeXMPPPasswordCb(self, data, profile):
-        xmpp_password = data[xml_tools.formEscape('xmpp_password')]
-        d = self.host.memory.setParam("Password", xmpp_password, "Connection", profile_key=profile)
+        xmpp_password = data[xml_tools.formEscape("xmpp_password")]
+        d = self.host.memory.setParam(
+            "Password", xmpp_password, "Connection", profile_key=profile
+        )
         d.addCallback(lambda dummy: self.host.connect(profile))
         d.addCallback(lambda dummy: {})
         d.addErrback(lambda dummy: self._changeXMPPPassword({}, profile))
--- a/sat/test/constants.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/test/constants.py	Wed Jun 27 20:14:46 2018 +0200
@@ -23,10 +23,22 @@
 
 class Const(object):
 
-    PROF_KEY_NONE = '@NONE@'
+    PROF_KEY_NONE = "@NONE@"
 
-    PROFILE = ['test_profile', 'test_profile2', 'test_profile3', 'test_profile4', 'test_profile5']
-    JID_STR = [u"test@example.org/SàT", u"sender@example.net/house", u"sender@example.net/work", u"sender@server.net/res", u"xxx@server.net/res"]
+    PROFILE = [
+        "test_profile",
+        "test_profile2",
+        "test_profile3",
+        "test_profile4",
+        "test_profile5",
+    ]
+    JID_STR = [
+        u"test@example.org/SàT",
+        u"sender@example.net/house",
+        u"sender@example.net/work",
+        u"sender@server.net/res",
+        u"xxx@server.net/res",
+    ]
     JID = [jid.JID(jid_s) for jid_s in JID_STR]
 
     PROFILE_DICT = {}
--- a/sat/test/helpers_plugins.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/test/helpers_plugins.py	Wed Jun 27 20:14:46 2018 +0200
@@ -57,8 +57,12 @@
             if other_profile == profile:
                 continue
             try:
-                other_room = self.plugin_parent.clients[other_profile].joined_rooms[room_jid]
-                roster.setdefault(other_room.nick, User(other_room.nick, C.PROFILE_DICT[other_profile]))
+                other_room = self.plugin_parent.clients[other_profile].joined_rooms[
+                    room_jid
+                ]
+                roster.setdefault(
+                    other_room.nick, User(other_room.nick, C.PROFILE_DICT[other_profile])
+                )
                 for other_nick in other_room.roster:
                     roster.setdefault(other_nick, other_room.roster[other_nick])
             except (AttributeError, KeyError):
@@ -80,8 +84,12 @@
             if other_profile == profile:
                 continue
             try:
-                other_room = self.plugin_parent.clients[other_profile].joined_rooms[room_jid]
-                other_room.roster.setdefault(room.nick, User(room.nick, C.PROFILE_DICT[profile]))
+                other_room = self.plugin_parent.clients[other_profile].joined_rooms[
+                    room_jid
+                ]
+                other_room.roster.setdefault(
+                    room.nick, User(room.nick, C.PROFILE_DICT[profile])
+                )
             except (AttributeError, KeyError):
                 pass
 
@@ -101,7 +109,9 @@
             if other_profile == profile:
                 continue
             try:
-                other_room = self.plugin_parent.clients[other_profile].joined_rooms[roomJID]
+                other_room = self.plugin_parent.clients[other_profile].joined_rooms[
+                    roomJID
+                ]
                 del other_room.roster[room.nick]
             except (AttributeError, KeyError):
                 pass
@@ -110,14 +120,13 @@
 
 
 class FakeXEP_0045(plugin_xep_0045.XEP_0045):
-
     def __init__(self, host):
         self.host = host
         self.clients = {}
         for profile in C.PROFILE:
             self.clients[profile] = FakeMUCClient(self)
 
-    def join(self, room_jid, nick, options={}, profile_key='@DEFAULT@'):
+    def join(self, room_jid, nick, options={}, profile_key="@DEFAULT@"):
         """
         @param roomJID: the room JID
         @param nick: nick to be used in the room
@@ -140,7 +149,7 @@
         self.join(muc_jid, nick, profile_key=profile)
         return self.getNick(muc_index, user_index)
 
-    def leave(self, room_jid, profile_key='@DEFAULT@'):
+    def leave(self, room_jid, profile_key="@DEFAULT@"):
         """
         @param roomJID: the room JID
         @param profile_key: the profile of the user leaving the room
@@ -174,7 +183,7 @@
         try:
             return self.getRoomNick(C.MUC[muc_index], C.PROFILE[user_index])
         except (KeyError, AttributeError):
-            return ''
+            return ""
 
     def getNickOfUser(self, muc_index, user_index, profile_index, secure=True):
         try:
@@ -185,11 +194,10 @@
 
 
 class FakeXEP_0249(object):
-
     def __init__(self, host):
         self.host = host
 
-    def invite(self, target, room, options={}, profile_key='@DEFAULT@'):
+    def invite(self, target, room, options={}, profile_key="@DEFAULT@"):
         """
         Invite a user to a room. To accept the invitation from a test,
         just call FakeXEP_0045.joinRoom (no need to have a dedicated method).
@@ -202,15 +210,13 @@
 
 
 class FakeSatPubSubClient(object):
-
     def __init__(self, host, parent_plugin):
         self.host = host
         self.parent_plugin = parent_plugin
         self.__items = OrderedDict()
         self.__rsm_responses = {}
 
-    def createNode(self, service, nodeIdentifier=None, options=None,
-                   sender=None):
+    def createNode(self, service, nodeIdentifier=None, options=None, sender=None):
         return defer.succeed(None)
 
     def deleteNode(self, service, nodeIdentifier, sender=None):
@@ -220,12 +226,17 @@
             pass
         return defer.succeed(None)
 
-    def subscribe(self, service, nodeIdentifier, subscriber,
-                  options=None, sender=None):
+    def subscribe(self, service, nodeIdentifier, subscriber, options=None, sender=None):
         return defer.succeed(None)
 
-    def unsubscribe(self, service, nodeIdentifier, subscriber,
-                    subscriptionIdentifier=None, sender=None):
+    def unsubscribe(
+        self,
+        service,
+        nodeIdentifier,
+        subscriber,
+        subscriptionIdentifier=None,
+        sender=None,
+    ):
         return defer.succeed(None)
 
     def publish(self, service, nodeIdentifier, items=None, sender=None):
@@ -234,7 +245,7 @@
         def replace(item_obj):
             index = 0
             for current in node:
-                if current['id'] == item_obj['id']:
+                if current["id"] == item_obj["id"]:
                     node[index] = item_obj
                     return True
                 index += 1
@@ -246,22 +257,30 @@
                 node.append(item_obj)
         return defer.succeed(None)
 
-    def items(self, service, nodeIdentifier, maxItems=None, itemIdentifiers=None,
-              subscriptionIdentifier=None, sender=None, ext_data=None):
+    def items(
+        self,
+        service,
+        nodeIdentifier,
+        maxItems=None,
+        itemIdentifiers=None,
+        subscriptionIdentifier=None,
+        sender=None,
+        ext_data=None,
+    ):
         try:
             items = self.__items[nodeIdentifier]
         except KeyError:
             items = []
         if ext_data:
-            assert('id' in ext_data)
-            if 'rsm' in ext_data:
-                args = (0, items[0]['id'], items[-1]['id']) if items else ()
-                self.__rsm_responses[ext_data['id']] = RSMResponse(len(items), *args)
+            assert "id" in ext_data
+            if "rsm" in ext_data:
+                args = (0, items[0]["id"], items[-1]["id"]) if items else ()
+                self.__rsm_responses[ext_data["id"]] = RSMResponse(len(items), *args)
         return defer.succeed(items)
 
     def retractItems(self, service, nodeIdentifier, itemIdentifiers, sender=None):
         node = self.__items[nodeIdentifier]
-        for item in [item for item in node if item['id'] in itemIdentifiers]:
+        for item in [item for item in node if item["id"] in itemIdentifiers]:
             node.remove(item)
         return defer.succeed(None)
 
--- a/sat/test/test_helpers_plugins.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/test/test_helpers_plugins.py	Wed Jun 27 20:14:46 2018 +0200
@@ -25,93 +25,100 @@
 
 
 class FakeXEP_0045Test(helpers.SatTestCase):
-
     def setUp(self):
         self.host = helpers.FakeSAT()
         self.plugin = helpers_plugins.FakeXEP_0045(self.host)
 
     def test_joinRoom(self):
         self.plugin.joinRoom(0, 0)
-        self.assertEqual('test', self.plugin.getNick(0, 0))
+        self.assertEqual("test", self.plugin.getNick(0, 0))
         self.assertEqual(None, self.plugin.getNickOfUser(0, 0, 0))
         self.assertEqual(None, self.plugin.getNickOfUser(0, 1, 0))
         self.assertEqual(None, self.plugin.getNickOfUser(0, 2, 0))
         self.assertEqual(None, self.plugin.getNickOfUser(0, 3, 0))
-        self.assertEqual('', self.plugin.getNick(0, 1))
+        self.assertEqual("", self.plugin.getNick(0, 1))
         self.assertEqual(None, self.plugin.getNickOfUser(0, 0, 1))
         self.assertEqual(None, self.plugin.getNickOfUser(0, 1, 1))
         self.assertEqual(None, self.plugin.getNickOfUser(0, 2, 1))
         self.assertEqual(None, self.plugin.getNickOfUser(0, 3, 1))
-        self.assertEqual('', self.plugin.getNick(0, 2))
+        self.assertEqual("", self.plugin.getNick(0, 2))
         self.assertEqual(None, self.plugin.getNickOfUser(0, 0, 2))
         self.assertEqual(None, self.plugin.getNickOfUser(0, 1, 2))
         self.assertEqual(None, self.plugin.getNickOfUser(0, 2, 2))
         self.assertEqual(None, self.plugin.getNickOfUser(0, 3, 2))
-        self.assertEqual('', self.plugin.getNick(0, 3))
+        self.assertEqual("", self.plugin.getNick(0, 3))
         self.assertEqual(None, self.plugin.getNickOfUser(0, 0, 3))
         self.assertEqual(None, self.plugin.getNickOfUser(0, 1, 3))
         self.assertEqual(None, self.plugin.getNickOfUser(0, 2, 3))
         self.assertEqual(None, self.plugin.getNickOfUser(0, 3, 3))
         self.plugin.joinRoom(0, 1)
-        self.assertEqual('test', self.plugin.getNick(0, 0))
+        self.assertEqual("test", self.plugin.getNick(0, 0))
         self.assertEqual(None, self.plugin.getNickOfUser(0, 0, 0))
-        self.assertEqual('sender', self.plugin.getNickOfUser(0, 1, 0))
-        self.assertEqual('sender', self.plugin.getNickOfUser(0, 2, 0))
+        self.assertEqual("sender", self.plugin.getNickOfUser(0, 1, 0))
+        self.assertEqual("sender", self.plugin.getNickOfUser(0, 2, 0))
         self.assertEqual(None, self.plugin.getNickOfUser(0, 3, 0))
-        self.assertEqual('sender', self.plugin.getNick(0, 1))
-        self.assertEqual('test', self.plugin.getNickOfUser(0, 0, 1))
+        self.assertEqual("sender", self.plugin.getNick(0, 1))
+        self.assertEqual("test", self.plugin.getNickOfUser(0, 0, 1))
         self.assertEqual(None, self.plugin.getNickOfUser(0, 1, 1))
         self.assertEqual(None, self.plugin.getNickOfUser(0, 2, 1))
         self.assertEqual(None, self.plugin.getNickOfUser(0, 3, 1))
-        self.assertEqual('', self.plugin.getNick(0, 2))
+        self.assertEqual("", self.plugin.getNick(0, 2))
         self.assertEqual(None, self.plugin.getNickOfUser(0, 0, 2))
         self.assertEqual(None, self.plugin.getNickOfUser(0, 1, 2))
         self.assertEqual(None, self.plugin.getNickOfUser(0, 2, 2))
         self.assertEqual(None, self.plugin.getNickOfUser(0, 3, 2))
-        self.assertEqual('', self.plugin.getNick(0, 3))
+        self.assertEqual("", self.plugin.getNick(0, 3))
         self.assertEqual(None, self.plugin.getNickOfUser(0, 0, 3))
         self.assertEqual(None, self.plugin.getNickOfUser(0, 1, 3))
         self.assertEqual(None, self.plugin.getNickOfUser(0, 2, 3))
         self.assertEqual(None, self.plugin.getNickOfUser(0, 3, 3))
         self.plugin.joinRoom(0, 2)
-        self.assertEqual('test', self.plugin.getNick(0, 0))
+        self.assertEqual("test", self.plugin.getNick(0, 0))
         self.assertEqual(None, self.plugin.getNickOfUser(0, 0, 0))
-        self.assertEqual('sender', self.plugin.getNickOfUser(0, 1, 0))
-        self.assertEqual('sender', self.plugin.getNickOfUser(0, 2, 0))
+        self.assertEqual("sender", self.plugin.getNickOfUser(0, 1, 0))
+        self.assertEqual("sender", self.plugin.getNickOfUser(0, 2, 0))
         self.assertEqual(None, self.plugin.getNickOfUser(0, 3, 0))
-        self.assertEqual('sender', self.plugin.getNick(0, 1))
-        self.assertEqual('test', self.plugin.getNickOfUser(0, 0, 1))
-        self.assertEqual('sender', self.plugin.getNickOfUser(0, 1, 1))  # Const.JID[2] is in the roster for Const.PROFILE[1]
-        self.assertEqual('sender', self.plugin.getNickOfUser(0, 2, 1))
+        self.assertEqual("sender", self.plugin.getNick(0, 1))
+        self.assertEqual("test", self.plugin.getNickOfUser(0, 0, 1))
+        self.assertEqual(
+            "sender", self.plugin.getNickOfUser(0, 1, 1)
+        )  # Const.JID[2] is in the roster for Const.PROFILE[1]
+        self.assertEqual("sender", self.plugin.getNickOfUser(0, 2, 1))
         self.assertEqual(None, self.plugin.getNickOfUser(0, 3, 1))
-        self.assertEqual('sender', self.plugin.getNick(0, 2))
-        self.assertEqual('test', self.plugin.getNickOfUser(0, 0, 2))
-        self.assertEqual('sender', self.plugin.getNickOfUser(0, 1, 2))
-        self.assertEqual('sender', self.plugin.getNickOfUser(0, 2, 2))   # Const.JID[1] is in the roster for Const.PROFILE[2]
+        self.assertEqual("sender", self.plugin.getNick(0, 2))
+        self.assertEqual("test", self.plugin.getNickOfUser(0, 0, 2))
+        self.assertEqual("sender", self.plugin.getNickOfUser(0, 1, 2))
+        self.assertEqual(
+            "sender", self.plugin.getNickOfUser(0, 2, 2)
+        )  # Const.JID[1] is in the roster for Const.PROFILE[2]
         self.assertEqual(None, self.plugin.getNickOfUser(0, 3, 2))
-        self.assertEqual('', self.plugin.getNick(0, 3))
+        self.assertEqual("", self.plugin.getNick(0, 3))
         self.assertEqual(None, self.plugin.getNickOfUser(0, 0, 3))
         self.assertEqual(None, self.plugin.getNickOfUser(0, 1, 3))
         self.assertEqual(None, self.plugin.getNickOfUser(0, 2, 3))
         self.assertEqual(None, self.plugin.getNickOfUser(0, 3, 3))
         self.plugin.joinRoom(0, 3)
-        self.assertEqual('test', self.plugin.getNick(0, 0))
+        self.assertEqual("test", self.plugin.getNick(0, 0))
         self.assertEqual(None, self.plugin.getNickOfUser(0, 0, 0))
-        self.assertEqual('sender', self.plugin.getNickOfUser(0, 1, 0))
-        self.assertEqual('sender', self.plugin.getNickOfUser(0, 2, 0))
-        self.assertEqual('sender_', self.plugin.getNickOfUser(0, 3, 0))
-        self.assertEqual('sender', self.plugin.getNick(0, 1))
-        self.assertEqual('test', self.plugin.getNickOfUser(0, 0, 1))
-        self.assertEqual('sender', self.plugin.getNickOfUser(0, 1, 1))  # Const.JID[2] is in the roster for Const.PROFILE[1]
-        self.assertEqual('sender', self.plugin.getNickOfUser(0, 2, 1))
-        self.assertEqual('sender_', self.plugin.getNickOfUser(0, 3, 1))
-        self.assertEqual('sender', self.plugin.getNick(0, 2))
-        self.assertEqual('test', self.plugin.getNickOfUser(0, 0, 2))
-        self.assertEqual('sender', self.plugin.getNickOfUser(0, 1, 2))
-        self.assertEqual('sender', self.plugin.getNickOfUser(0, 2, 2))   # Const.JID[1] is in the roster for Const.PROFILE[2]
-        self.assertEqual('sender_', self.plugin.getNickOfUser(0, 3, 2))
-        self.assertEqual('sender_', self.plugin.getNick(0, 3))
-        self.assertEqual('test', self.plugin.getNickOfUser(0, 0, 3))
-        self.assertEqual('sender', self.plugin.getNickOfUser(0, 1, 3))
-        self.assertEqual('sender', self.plugin.getNickOfUser(0, 2, 3))
+        self.assertEqual("sender", self.plugin.getNickOfUser(0, 1, 0))
+        self.assertEqual("sender", self.plugin.getNickOfUser(0, 2, 0))
+        self.assertEqual("sender_", self.plugin.getNickOfUser(0, 3, 0))
+        self.assertEqual("sender", self.plugin.getNick(0, 1))
+        self.assertEqual("test", self.plugin.getNickOfUser(0, 0, 1))
+        self.assertEqual(
+            "sender", self.plugin.getNickOfUser(0, 1, 1)
+        )  # Const.JID[2] is in the roster for Const.PROFILE[1]
+        self.assertEqual("sender", self.plugin.getNickOfUser(0, 2, 1))
+        self.assertEqual("sender_", self.plugin.getNickOfUser(0, 3, 1))
+        self.assertEqual("sender", self.plugin.getNick(0, 2))
+        self.assertEqual("test", self.plugin.getNickOfUser(0, 0, 2))
+        self.assertEqual("sender", self.plugin.getNickOfUser(0, 1, 2))
+        self.assertEqual(
+            "sender", self.plugin.getNickOfUser(0, 2, 2)
+        )  # Const.JID[1] is in the roster for Const.PROFILE[2]
+        self.assertEqual("sender_", self.plugin.getNickOfUser(0, 3, 2))
+        self.assertEqual("sender_", self.plugin.getNick(0, 3))
+        self.assertEqual("test", self.plugin.getNickOfUser(0, 0, 3))
+        self.assertEqual("sender", self.plugin.getNickOfUser(0, 1, 3))
+        self.assertEqual("sender", self.plugin.getNickOfUser(0, 2, 3))
         self.assertEqual(None, self.plugin.getNickOfUser(0, 3, 3))
--- a/sat/test/test_memory.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/test/test_memory.py	Wed Jun 27 20:14:46 2018 +0200
@@ -26,7 +26,6 @@
 
 
 class MemoryTest(unittest.TestCase):
-
     def setUp(self):
         self.host = helpers.FakeSAT()
 
@@ -37,14 +36,19 @@
         @param security_level: security level of the parameters
         @return (str)
         """
+
         def getParam(name):
             return """
             <param name="%(param_name)s" label="%(param_label)s" value="true" type="bool" %(security)s/>
-            """ % {'param_name': name,
-                            'param_label': _(name),
-                            'security': '' if security_level is None else ('security="%d"' % security_level)
-                            }
-        params = ''
+            """ % {
+                "param_name": name,
+                "param_label": _(name),
+                "security": ""
+                if security_level is None
+                else ('security="%d"' % security_level),
+            }
+
+        params = ""
         if "1" in param:
             params += getParam(Const.ENABLE_UNIBOX_PARAM)
         if "2" in param:
@@ -60,9 +64,9 @@
         </individual>
         </params>
         """ % {
-            'category_name': Const.COMPOSITION_KEY,
-            'category_label': _(Const.COMPOSITION_KEY),
-            'params': params
+            "category_name": Const.COMPOSITION_KEY,
+            "category_label": _(Const.COMPOSITION_KEY),
+            "params": params,
         }
 
     def _paramExists(self, param="1", src=None):
@@ -86,7 +90,10 @@
             if type_node.nodeName not in ("individual", "general", "params"):
                 continue
             for cat_node in type_node.childNodes:
-                if cat_node.nodeName != "category" or cat_node.getAttribute("name") != category:
+                if (
+                    cat_node.nodeName != "category"
+                    or cat_node.getAttribute("name") != category
+                ):
                     continue
                 for param in cat_node.childNodes:
                     if param.nodeName == "param" and param.getAttribute("name") == name:
@@ -100,7 +107,11 @@
         @param exists (boolean): True to assert the param exists, False to assert it doesn't
         @param deferred (boolean): True if this method is called from a Deferred callback
         """
-        msg = "Expected parameter not found!\n" if exists else "Unexpected parameter found!\n"
+        msg = (
+            "Expected parameter not found!\n"
+            if exists
+            else "Unexpected parameter found!\n"
+        )
         if deferred:
             # in this stack we can see the line where the error came from,
             # if limit=5, 6 is not enough you can increase the value
@@ -116,21 +127,25 @@
 
     def assertParamExists_async(self, src, param="1"):
         """@param src: a deferred result from Memory.getParams"""
-        self.assertParam_generic(param, minidom.parseString(src.encode("utf-8")), True, True)
+        self.assertParam_generic(
+            param, minidom.parseString(src.encode("utf-8")), True, True
+        )
 
     def assertParamNotExists_async(self, src, param="1"):
         """@param src: a deferred result from Memory.getParams"""
-        self.assertParam_generic(param, minidom.parseString(src.encode("utf-8")), False, True)
+        self.assertParam_generic(
+            param, minidom.parseString(src.encode("utf-8")), False, True
+        )
 
-    def _getParams(self, security_limit, app='', profile_key='@NONE@'):
+    def _getParams(self, security_limit, app="", profile_key="@NONE@"):
         """Get the parameters accessible with the given security limit and application name.
 
         @param security_limit (int): the security limit
         @param app (str): empty string or "libervia"
         @param profile_key
         """
-        if profile_key == '@NONE@':
-            profile_key = '@DEFAULT@'
+        if profile_key == "@NONE@":
+            profile_key = "@DEFAULT@"
         return self.host.memory.params.getParams(security_limit, app, profile_key)
 
     def test_updateParams(self):
@@ -141,14 +156,17 @@
         previous = self.host.memory.params.dom.cloneNode(True)
         # now check if it is really updated and not duplicated
         self.host.memory.updateParams(self._getParamXML())
-        self.assertEqual(previous.toxml().encode("utf-8"), self.host.memory.params.dom.toxml().encode("utf-8"))
+        self.assertEqual(
+            previous.toxml().encode("utf-8"),
+            self.host.memory.params.dom.toxml().encode("utf-8"),
+        )
 
         self.host.memory.reinit()
         # check successive updates (without intersection)
-        self.host.memory.updateParams(self._getParamXML('1'))
+        self.host.memory.updateParams(self._getParamXML("1"))
         self.assertParamExists("1")
         self.assertParamNotExists("2")
-        self.host.memory.updateParams(self._getParamXML('2'))
+        self.host.memory.updateParams(self._getParamXML("2"))
         self.assertParamExists("1")
         self.assertParamExists("2")
 
@@ -156,19 +174,22 @@
 
         self.host.memory.reinit()
         # check successive updates (with intersection)
-        self.host.memory.updateParams(self._getParamXML('1'))
+        self.host.memory.updateParams(self._getParamXML("1"))
         self.assertParamExists("1")
         self.assertParamNotExists("2")
-        self.host.memory.updateParams(self._getParamXML('12'))
+        self.host.memory.updateParams(self._getParamXML("12"))
         self.assertParamExists("1")
         self.assertParamExists("2")
 
         # successive updates with or without intersection should have the same result
-        self.assertEqual(previous.toxml().encode("utf-8"), self.host.memory.params.dom.toxml().encode("utf-8"))
+        self.assertEqual(
+            previous.toxml().encode("utf-8"),
+            self.host.memory.params.dom.toxml().encode("utf-8"),
+        )
 
         self.host.memory.reinit()
         # one update with two params in a new category
-        self.host.memory.updateParams(self._getParamXML('12'))
+        self.host.memory.updateParams(self._getParamXML("12"))
         self.assertParamExists("1")
         self.assertParamExists("2")
 
@@ -196,7 +217,6 @@
         return self._getParams(1).addCallback(self.assertParamExists_async)
 
     def test_paramsRegisterApp(self):
-
         def register(xml, security_limit, app):
             """
             @param xml: XML definition of the parameters to be added
@@ -271,17 +291,23 @@
         self.host.memory.reinit()
         params = self._getParamXML(security_level=1)
         self.host.memory.paramsRegisterApp(params, 1, Const.APP_NAME)
-        self._getParams(1, '').addCallback(self.assertParamExists_async)
+        self._getParams(1, "").addCallback(self.assertParamExists_async)
         self._getParams(1, Const.APP_NAME).addCallback(self.assertParamExists_async)
-        self._getParams(1, 'another_dummy_frontend').addCallback(self.assertParamNotExists_async)
+        self._getParams(1, "another_dummy_frontend").addCallback(
+            self.assertParamNotExists_async
+        )
 
         # the same with several parameters registered at the same time
         self.host.memory.reinit()
-        params = self._getParamXML('12', security_level=0)
+        params = self._getParamXML("12", security_level=0)
         self.host.memory.paramsRegisterApp(params, 5, Const.APP_NAME)
-        self._getParams(5, '').addCallback(self.assertParamExists_async)
-        self._getParams(5, '').addCallback(self.assertParamExists_async, "2")
+        self._getParams(5, "").addCallback(self.assertParamExists_async)
+        self._getParams(5, "").addCallback(self.assertParamExists_async, "2")
         self._getParams(5, Const.APP_NAME).addCallback(self.assertParamExists_async)
         self._getParams(5, Const.APP_NAME).addCallback(self.assertParamExists_async, "2")
-        self._getParams(5, 'another_dummy_frontend').addCallback(self.assertParamNotExists_async)
-        return self._getParams(5, 'another_dummy_frontend').addCallback(self.assertParamNotExists_async, "2")
+        self._getParams(5, "another_dummy_frontend").addCallback(
+            self.assertParamNotExists_async
+        )
+        return self._getParams(5, "another_dummy_frontend").addCallback(
+            self.assertParamNotExists_async, "2"
+        )
--- a/sat/test/test_memory_crypto.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/test/test_memory_crypto.py	Wed Jun 27 20:14:46 2018 +0200
@@ -30,11 +30,10 @@
 
 def getRandomUnicode(len):
     """Return a random unicode string"""
-    return u''.join(random.choice(string.letters + u"éáúóâêûôßüöä") for i in xrange(len))
+    return u"".join(random.choice(string.letters + u"éáúóâêûôßüöä") for i in xrange(len))
 
 
 class CryptoTest(helpers.SatTestCase):
-
     def setUp(self):
         self.host = helpers.FakeSAT()
 
--- a/sat/test/test_plugin_misc_groupblog.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/test/test_plugin_misc_groupblog.py	Wed Jun 27 20:14:46 2018 +0200
@@ -31,27 +31,32 @@
 from twisted.words.protocols.jabber import jid
 
 
-NS_PUBSUB = 'http://jabber.org/protocol/pubsub'
+NS_PUBSUB = "http://jabber.org/protocol/pubsub"
 
 DO_NOT_COUNT_COMMENTS = -1
 
-SERVICE = u'pubsub.example.com'
-PUBLISHER = u'test@example.org'
-OTHER_PUBLISHER = u'other@xmpp.net'
-NODE_ID = u'urn:xmpp:groupblog:{publisher}'.format(publisher=PUBLISHER)
-OTHER_NODE_ID = u'urn:xmpp:groupblog:{publisher}'.format(publisher=OTHER_PUBLISHER)
-ITEM_ID_1 = u'c745a688-9b02-11e3-a1a3-c0143dd4fe51'
-COMMENT_ID_1 = u'd745a688-9b02-11e3-a1a3-c0143dd4fe52'
-COMMENT_ID_2 = u'e745a688-9b02-11e3-a1a3-c0143dd4fe53'
+SERVICE = u"pubsub.example.com"
+PUBLISHER = u"test@example.org"
+OTHER_PUBLISHER = u"other@xmpp.net"
+NODE_ID = u"urn:xmpp:groupblog:{publisher}".format(publisher=PUBLISHER)
+OTHER_NODE_ID = u"urn:xmpp:groupblog:{publisher}".format(publisher=OTHER_PUBLISHER)
+ITEM_ID_1 = u"c745a688-9b02-11e3-a1a3-c0143dd4fe51"
+COMMENT_ID_1 = u"d745a688-9b02-11e3-a1a3-c0143dd4fe52"
+COMMENT_ID_2 = u"e745a688-9b02-11e3-a1a3-c0143dd4fe53"
 
 
 def COMMENTS_NODE_ID(publisher=PUBLISHER):
-    return u'urn:xmpp:comments:_{id}__urn:xmpp:groupblog:{publisher}'.format(id=ITEM_ID_1, publisher=publisher)
+    return u"urn:xmpp:comments:_{id}__urn:xmpp:groupblog:{publisher}".format(
+        id=ITEM_ID_1, publisher=publisher
+    )
 
 
 def COMMENTS_NODE_URL(publisher=PUBLISHER):
-    return u'xmpp:{service}?node={node}'.format(service=SERVICE, id=ITEM_ID_1,
-                                                node=COMMENTS_NODE_ID(publisher).replace(':', '%3A').replace('@', '%40'))
+    return u"xmpp:{service}?node={node}".format(
+        service=SERVICE,
+        id=ITEM_ID_1,
+        node=COMMENTS_NODE_ID(publisher).replace(":", "%3A").replace("@", "%40"),
+    )
 
 
 def ITEM(publisher=PUBLISHER):
@@ -68,7 +73,12 @@
               </author>
             </entry>
           </item>
-        """.format(ns=NS_PUBSUB, id=ITEM_ID_1, publisher=publisher, comments_node_url=COMMENTS_NODE_URL(publisher))
+        """.format(
+        ns=NS_PUBSUB,
+        id=ITEM_ID_1,
+        publisher=publisher,
+        comments_node_url=COMMENTS_NODE_URL(publisher),
+    )
 
 
 def COMMENT(id_=COMMENT_ID_1):
@@ -84,35 +94,41 @@
               </author>
             </entry>
           </item>
-        """.format(ns=NS_PUBSUB, id=id_, publisher=PUBLISHER)
+        """.format(
+        ns=NS_PUBSUB, id=id_, publisher=PUBLISHER
+    )
 
 
 def ITEM_DATA(id_=ITEM_ID_1, count=0):
-    res = {'id': ITEM_ID_1,
-           'type': 'main_item',
-           'content': 'The Uses of This World',
-           'author': PUBLISHER,
-           'updated': '1071251243.0',
-           'published': '1071251243.0',
-           'service': SERVICE,
-           'comments': COMMENTS_NODE_URL_1,
-           'comments_service': SERVICE,
-           'comments_node': COMMENTS_NODE_ID_1}
+    res = {
+        "id": ITEM_ID_1,
+        "type": "main_item",
+        "content": "The Uses of This World",
+        "author": PUBLISHER,
+        "updated": "1071251243.0",
+        "published": "1071251243.0",
+        "service": SERVICE,
+        "comments": COMMENTS_NODE_URL_1,
+        "comments_service": SERVICE,
+        "comments_node": COMMENTS_NODE_ID_1,
+    }
     if count != DO_NOT_COUNT_COMMENTS:
-        res.update({'comments_count': unicode(count)})
+        res.update({"comments_count": unicode(count)})
     return res
 
 
 def COMMENT_DATA(id_=COMMENT_ID_1):
-    return {'id': id_,
-            'type': 'comment',
-            'content': 'The Uses of This World',
-            'author': PUBLISHER,
-            'updated': '1071251243.0',
-            'published': '1071251243.0',
-            'service': SERVICE,
-            'node': COMMENTS_NODE_ID_1,
-            'verified_publisher': 'false'}
+    return {
+        "id": id_,
+        "type": "comment",
+        "content": "The Uses of This World",
+        "author": PUBLISHER,
+        "updated": "1071251243.0",
+        "published": "1071251243.0",
+        "service": SERVICE,
+        "node": COMMENTS_NODE_ID_1,
+        "verified_publisher": "false",
+    }
 
 
 COMMENTS_NODE_ID_1 = COMMENTS_NODE_ID()
@@ -128,19 +144,21 @@
 def ITEM_DATA_1(count=0):
     return ITEM_DATA(count=count)
 
+
 COMMENT_DATA_1 = COMMENT_DATA()
 COMMENT_DATA_2 = COMMENT_DATA(COMMENT_ID_2)
 
 
 class XEP_groupblogTest(helpers.SatTestCase):
-
     def setUp(self):
         self.host = helpers.FakeSAT()
-        self.host.plugins['XEP-0060'] = plugin_xep_0060.XEP_0060(self.host)
-        self.host.plugins['XEP-0163'] = plugin_xep_0163.XEP_0163(self.host)
+        self.host.plugins["XEP-0060"] = plugin_xep_0060.XEP_0060(self.host)
+        self.host.plugins["XEP-0163"] = plugin_xep_0163.XEP_0163(self.host)
         reload(plugin_misc_text_syntaxes)  # reload the plugin to avoid conflict error
-        self.host.plugins['TEXT-SYNTAXES'] = plugin_misc_text_syntaxes.TextSyntaxes(self.host)
-        self.host.plugins['XEP-0277'] = plugin_xep_0277.XEP_0277(self.host)
+        self.host.plugins["TEXT-SYNTAXES"] = plugin_misc_text_syntaxes.TextSyntaxes(
+            self.host
+        )
+        self.host.plugins["XEP-0277"] = plugin_xep_0277.XEP_0277(self.host)
         self.plugin = plugin_misc_groupblog.GroupBlog(self.host)
         self.plugin._initialise = self._initialise
         self.__initialised = False
@@ -151,8 +169,10 @@
         client = self.host.getClient(profile)
         if not self.__initialised:
             client.item_access_pubsub = jid.JID(SERVICE)
-            xep_0060 = self.host.plugins['XEP-0060']
-            client.pubsub_client = helpers_plugins.FakeSatPubSubClient(self.host, xep_0060)
+            xep_0060 = self.host.plugins["XEP-0060"]
+            client.pubsub_client = helpers_plugins.FakeSatPubSubClient(
+                self.host, xep_0060
+            )
             client.pubsub_client.parent = client
             self.psclient = client.pubsub_client
             helpers.FakeSAT.getDiscoItems = self.psclient.service_getDiscoItems
@@ -167,19 +187,30 @@
         self._initialise(C.PROFILE[0])
         d = self.psclient.items(SERVICE, NODE_ID)
         d.addCallback(lambda items: self.assertEqual(len(items), 0))
-        d.addCallback(lambda dummy: self.plugin.sendGroupBlog('PUBLIC', [], 'test', {}, C.PROFILE[0]))
+        d.addCallback(
+            lambda dummy: self.plugin.sendGroupBlog(
+                "PUBLIC", [], "test", {}, C.PROFILE[0]
+            )
+        )
         d.addCallback(lambda dummy: self.psclient.items(SERVICE, NODE_ID))
         return d.addCallback(lambda items: self.assertEqual(len(items), 1))
 
     def test_deleteGroupBlog(self):
         pub_data = (SERVICE, NODE_ID, ITEM_ID_1)
-        self.host.bridge.expectCall('personalEvent', C.JID_STR[0],
-                                    "MICROBLOG_DELETE",
-                                    {'type': 'main_item', 'id': ITEM_ID_1},
-                                    C.PROFILE[0])
+        self.host.bridge.expectCall(
+            "personalEvent",
+            C.JID_STR[0],
+            "MICROBLOG_DELETE",
+            {"type": "main_item", "id": ITEM_ID_1},
+            C.PROFILE[0],
+        )
 
         d = self.psclient.publish(SERVICE, NODE_ID, [ITEM_1])
-        d.addCallback(lambda dummy: self.plugin.deleteGroupBlog(pub_data, COMMENTS_NODE_URL_1, profile_key=C.PROFILE[0]))
+        d.addCallback(
+            lambda dummy: self.plugin.deleteGroupBlog(
+                pub_data, COMMENTS_NODE_URL_1, profile_key=C.PROFILE[0]
+            )
+        )
         return d.addCallback(self.assertEqual, None)
 
     def test_updateGroupBlog(self):
@@ -188,71 +219,155 @@
 
         self._initialise(C.PROFILE[0])
         d = self.psclient.publish(SERVICE, NODE_ID, [ITEM_1])
-        d.addCallback(lambda dummy: self.plugin.updateGroupBlog(pub_data, COMMENTS_NODE_URL_1, new_text, {}, profile_key=C.PROFILE[0]))
+        d.addCallback(
+            lambda dummy: self.plugin.updateGroupBlog(
+                pub_data, COMMENTS_NODE_URL_1, new_text, {}, profile_key=C.PROFILE[0]
+            )
+        )
         d.addCallback(lambda dummy: self.psclient.items(SERVICE, NODE_ID))
-        return d.addCallback(lambda items: self.assertEqual(''.join(items[0].entry.title.children), new_text))
+        return d.addCallback(
+            lambda items: self.assertEqual(
+                "".join(items[0].entry.title.children), new_text
+            )
+        )
 
     def test_sendGroupBlogComment(self):
         self._initialise(C.PROFILE[0])
         d = self.psclient.items(SERVICE, NODE_ID)
         d.addCallback(lambda items: self.assertEqual(len(items), 0))
-        d.addCallback(lambda dummy: self.plugin.sendGroupBlogComment(COMMENTS_NODE_URL_1, 'test', {}, profile_key=C.PROFILE[0]))
+        d.addCallback(
+            lambda dummy: self.plugin.sendGroupBlogComment(
+                COMMENTS_NODE_URL_1, "test", {}, profile_key=C.PROFILE[0]
+            )
+        )
         d.addCallback(lambda dummy: self.psclient.items(SERVICE, COMMENTS_NODE_ID_1))
         return d.addCallback(lambda items: self.assertEqual(len(items), 1))
 
     def test_getGroupBlogs(self):
         self._initialise(C.PROFILE[0])
         d = self.psclient.publish(SERVICE, NODE_ID, [ITEM_1])
-        d.addCallback(lambda dummy: self.plugin.getGroupBlogs(PUBLISHER, profile_key=C.PROFILE[0]))
-        result = ([ITEM_DATA_1()], {'count': '1', 'index': '0', 'first': ITEM_ID_1, 'last': ITEM_ID_1})
+        d.addCallback(
+            lambda dummy: self.plugin.getGroupBlogs(PUBLISHER, profile_key=C.PROFILE[0])
+        )
+        result = (
+            [ITEM_DATA_1()],
+            {"count": "1", "index": "0", "first": ITEM_ID_1, "last": ITEM_ID_1},
+        )
         return d.addCallback(self.assertEqual, result)
 
     def test_getGroupBlogsNoCount(self):
         self._initialise(C.PROFILE[0])
         d = self.psclient.publish(SERVICE, NODE_ID, [ITEM_1])
-        d.addCallback(lambda dummy: self.plugin.getGroupBlogs(PUBLISHER, count_comments=False, profile_key=C.PROFILE[0]))
-        result = ([ITEM_DATA_1(DO_NOT_COUNT_COMMENTS)], {'count': '1', 'index': '0', 'first': ITEM_ID_1, 'last': ITEM_ID_1})
+        d.addCallback(
+            lambda dummy: self.plugin.getGroupBlogs(
+                PUBLISHER, count_comments=False, profile_key=C.PROFILE[0]
+            )
+        )
+        result = (
+            [ITEM_DATA_1(DO_NOT_COUNT_COMMENTS)],
+            {"count": "1", "index": "0", "first": ITEM_ID_1, "last": ITEM_ID_1},
+        )
         return d.addCallback(self.assertEqual, result)
 
     def test_getGroupBlogsWithIDs(self):
         self._initialise(C.PROFILE[0])
         d = self.psclient.publish(SERVICE, NODE_ID, [ITEM_1])
-        d.addCallback(lambda dummy: self.plugin.getGroupBlogs(PUBLISHER, [ITEM_ID_1], profile_key=C.PROFILE[0]))
-        result = ([ITEM_DATA_1()], {'count': '1', 'index': '0', 'first': ITEM_ID_1, 'last': ITEM_ID_1})
+        d.addCallback(
+            lambda dummy: self.plugin.getGroupBlogs(
+                PUBLISHER, [ITEM_ID_1], profile_key=C.PROFILE[0]
+            )
+        )
+        result = (
+            [ITEM_DATA_1()],
+            {"count": "1", "index": "0", "first": ITEM_ID_1, "last": ITEM_ID_1},
+        )
         return d.addCallback(self.assertEqual, result)
 
     def test_getGroupBlogsWithRSM(self):
         self._initialise(C.PROFILE[0])
         d = self.psclient.publish(SERVICE, NODE_ID, [ITEM_1])
-        d.addCallback(lambda dummy: self.plugin.getGroupBlogs(PUBLISHER, rsm_data={'max_': 1}, profile_key=C.PROFILE[0]))
-        result = ([ITEM_DATA_1()], {'count': '1', 'index': '0', 'first': ITEM_ID_1, 'last': ITEM_ID_1})
+        d.addCallback(
+            lambda dummy: self.plugin.getGroupBlogs(
+                PUBLISHER, rsm_data={"max_": 1}, profile_key=C.PROFILE[0]
+            )
+        )
+        result = (
+            [ITEM_DATA_1()],
+            {"count": "1", "index": "0", "first": ITEM_ID_1, "last": ITEM_ID_1},
+        )
         return d.addCallback(self.assertEqual, result)
 
     def test_getGroupBlogsWithComments(self):
         self._initialise(C.PROFILE[0])
         d = self.psclient.publish(SERVICE, NODE_ID, [ITEM_1])
-        d.addCallback(lambda dummy: self.psclient.publish(SERVICE, COMMENTS_NODE_ID_1, [COMMENT_1]))
-        d.addCallback(lambda dummy: self.plugin.getGroupBlogsWithComments(PUBLISHER, [], profile_key=C.PROFILE[0]))
-        result = ([(ITEM_DATA_1(1), ([COMMENT_DATA_1],
-                                     {'count': '1', 'index': '0', 'first': COMMENT_ID_1, 'last': COMMENT_ID_1}))],
-                  {'count': '1', 'index': '0', 'first': ITEM_ID_1, 'last': ITEM_ID_1})
+        d.addCallback(
+            lambda dummy: self.psclient.publish(SERVICE, COMMENTS_NODE_ID_1, [COMMENT_1])
+        )
+        d.addCallback(
+            lambda dummy: self.plugin.getGroupBlogsWithComments(
+                PUBLISHER, [], profile_key=C.PROFILE[0]
+            )
+        )
+        result = (
+            [
+                (
+                    ITEM_DATA_1(1),
+                    (
+                        [COMMENT_DATA_1],
+                        {
+                            "count": "1",
+                            "index": "0",
+                            "first": COMMENT_ID_1,
+                            "last": COMMENT_ID_1,
+                        },
+                    ),
+                )
+            ],
+            {"count": "1", "index": "0", "first": ITEM_ID_1, "last": ITEM_ID_1},
+        )
         return d.addCallback(self.assertEqual, result)
 
     def test_getGroupBlogsWithComments2(self):
         self._initialise(C.PROFILE[0])
         d = self.psclient.publish(SERVICE, NODE_ID, [ITEM_1])
-        d.addCallback(lambda dummy: self.psclient.publish(SERVICE, COMMENTS_NODE_ID_1, [COMMENT_1, COMMENT_2]))
-        d.addCallback(lambda dummy: self.plugin.getGroupBlogsWithComments(PUBLISHER, [], profile_key=C.PROFILE[0]))
-        result = ([(ITEM_DATA_1(2), ([COMMENT_DATA_1, COMMENT_DATA_2],
-                                     {'count': '2', 'index': '0', 'first': COMMENT_ID_1, 'last': COMMENT_ID_2}))],
-                  {'count': '1', 'index': '0', 'first': ITEM_ID_1, 'last': ITEM_ID_1})
+        d.addCallback(
+            lambda dummy: self.psclient.publish(
+                SERVICE, COMMENTS_NODE_ID_1, [COMMENT_1, COMMENT_2]
+            )
+        )
+        d.addCallback(
+            lambda dummy: self.plugin.getGroupBlogsWithComments(
+                PUBLISHER, [], profile_key=C.PROFILE[0]
+            )
+        )
+        result = (
+            [
+                (
+                    ITEM_DATA_1(2),
+                    (
+                        [COMMENT_DATA_1, COMMENT_DATA_2],
+                        {
+                            "count": "2",
+                            "index": "0",
+                            "first": COMMENT_ID_1,
+                            "last": COMMENT_ID_2,
+                        },
+                    ),
+                )
+            ],
+            {"count": "1", "index": "0", "first": ITEM_ID_1, "last": ITEM_ID_1},
+        )
 
         return d.addCallback(self.assertEqual, result)
 
     def test_getGroupBlogsAtom(self):
         self._initialise(C.PROFILE[0])
         d = self.psclient.publish(SERVICE, NODE_ID, [ITEM_1])
-        d.addCallback(lambda dummy: self.plugin.getGroupBlogsAtom(PUBLISHER, {'max_': 1}, profile_key=C.PROFILE[0]))
+        d.addCallback(
+            lambda dummy: self.plugin.getGroupBlogsAtom(
+                PUBLISHER, {"max_": 1}, profile_key=C.PROFILE[0]
+            )
+        )
 
         def cb(atom):
             self.assertIsInstance(atom, unicode)
@@ -263,11 +378,22 @@
     def test_getMassiveGroupBlogs(self):
         self._initialise(C.PROFILE[0])
         d = self.psclient.publish(SERVICE, NODE_ID, [ITEM_1])
-        d.addCallback(lambda dummy: self.plugin.getMassiveGroupBlogs('JID', [jid.JID(PUBLISHER)], {'max_': 1}, profile_key=C.PROFILE[0]))
-        result = {PUBLISHER: ([ITEM_DATA_1()], {'count': '1', 'index': '0', 'first': ITEM_ID_1, 'last': ITEM_ID_1})}
+        d.addCallback(
+            lambda dummy: self.plugin.getMassiveGroupBlogs(
+                "JID", [jid.JID(PUBLISHER)], {"max_": 1}, profile_key=C.PROFILE[0]
+            )
+        )
+        result = {
+            PUBLISHER: (
+                [ITEM_DATA_1()],
+                {"count": "1", "index": "0", "first": ITEM_ID_1, "last": ITEM_ID_1},
+            )
+        }
 
         def clean(res):
-            del self.host.plugins['XEP-0060'].node_cache[C.PROFILE[0] + '@found@' + SERVICE]
+            del self.host.plugins["XEP-0060"].node_cache[
+                C.PROFILE[0] + "@found@" + SERVICE
+            ]
             return res
 
         d.addCallback(clean)
@@ -276,12 +402,27 @@
     def test_getMassiveGroupBlogsWithComments(self):
         self._initialise(C.PROFILE[0])
         d = self.psclient.publish(SERVICE, NODE_ID, [ITEM_1])
-        d.addCallback(lambda dummy: self.psclient.publish(SERVICE, COMMENTS_NODE_ID_1, [COMMENT_1, COMMENT_2]))
-        d.addCallback(lambda dummy: self.plugin.getMassiveGroupBlogs('JID', [jid.JID(PUBLISHER)], {'max_': 1}, profile_key=C.PROFILE[0]))
-        result = {PUBLISHER: ([ITEM_DATA_1(2)], {'count': '1', 'index': '0', 'first': ITEM_ID_1, 'last': ITEM_ID_1})}
+        d.addCallback(
+            lambda dummy: self.psclient.publish(
+                SERVICE, COMMENTS_NODE_ID_1, [COMMENT_1, COMMENT_2]
+            )
+        )
+        d.addCallback(
+            lambda dummy: self.plugin.getMassiveGroupBlogs(
+                "JID", [jid.JID(PUBLISHER)], {"max_": 1}, profile_key=C.PROFILE[0]
+            )
+        )
+        result = {
+            PUBLISHER: (
+                [ITEM_DATA_1(2)],
+                {"count": "1", "index": "0", "first": ITEM_ID_1, "last": ITEM_ID_1},
+            )
+        }
 
         def clean(res):
-            del self.host.plugins['XEP-0060'].node_cache[C.PROFILE[0] + '@found@' + SERVICE]
+            del self.host.plugins["XEP-0060"].node_cache[
+                C.PROFILE[0] + "@found@" + SERVICE
+            ]
             return res
 
         d.addCallback(clean)
@@ -290,9 +431,18 @@
     def test_getGroupBlogComments(self):
         self._initialise(C.PROFILE[0])
         d = self.psclient.publish(SERVICE, NODE_ID, [ITEM_1])
-        d.addCallback(lambda dummy: self.psclient.publish(SERVICE, COMMENTS_NODE_ID_1, [COMMENT_1]))
-        d.addCallback(lambda dummy: self.plugin.getGroupBlogComments(SERVICE, COMMENTS_NODE_ID_1, {'max_': 1}, profile_key=C.PROFILE[0]))
-        result = ([COMMENT_DATA_1], {'count': '1', 'index': '0', 'first': COMMENT_ID_1, 'last': COMMENT_ID_1})
+        d.addCallback(
+            lambda dummy: self.psclient.publish(SERVICE, COMMENTS_NODE_ID_1, [COMMENT_1])
+        )
+        d.addCallback(
+            lambda dummy: self.plugin.getGroupBlogComments(
+                SERVICE, COMMENTS_NODE_ID_1, {"max_": 1}, profile_key=C.PROFILE[0]
+            )
+        )
+        result = (
+            [COMMENT_DATA_1],
+            {"count": "1", "index": "0", "first": COMMENT_ID_1, "last": COMMENT_ID_1},
+        )
         return d.addCallback(self.assertEqual, result)
 
     def test_subscribeGroupBlog(self):
@@ -302,11 +452,17 @@
 
     def test_massiveSubscribeGroupBlogs(self):
         self._initialise(C.PROFILE[0])
-        d = self.plugin.massiveSubscribeGroupBlogs('JID', [jid.JID(PUBLISHER)], profile_key=C.PROFILE[0])
+        d = self.plugin.massiveSubscribeGroupBlogs(
+            "JID", [jid.JID(PUBLISHER)], profile_key=C.PROFILE[0]
+        )
 
         def clean(res):
-            del self.host.plugins['XEP-0060'].node_cache[C.PROFILE[0] + '@found@' + SERVICE]
-            del self.host.plugins['XEP-0060'].node_cache[C.PROFILE[0] + '@subscriptions@' + SERVICE]
+            del self.host.plugins["XEP-0060"].node_cache[
+                C.PROFILE[0] + "@found@" + SERVICE
+            ]
+            del self.host.plugins["XEP-0060"].node_cache[
+                C.PROFILE[0] + "@subscriptions@" + SERVICE
+            ]
             return res
 
         d.addCallback(clean)
@@ -317,21 +473,33 @@
         self._initialise(C.PROFILE[0])
         self.host.profiles[C.PROFILE[0]].roster.addItem(jid.JID(OTHER_PUBLISHER))
         d = self.psclient.publish(SERVICE, NODE_ID, [ITEM_1])
-        d.addCallback(lambda dummy: self.psclient.publish(SERVICE, COMMENTS_NODE_ID_1, [COMMENT_1, COMMENT_2]))
+        d.addCallback(
+            lambda dummy: self.psclient.publish(
+                SERVICE, COMMENTS_NODE_ID_1, [COMMENT_1, COMMENT_2]
+            )
+        )
         d.addCallback(lambda dummy: self.psclient.items(SERVICE, NODE_ID))
         d.addCallback(lambda items: self.assertEqual(len(items), 1))
         d.addCallback(lambda dummy: self.psclient.items(SERVICE, COMMENTS_NODE_ID_1))
         d.addCallback(lambda items: self.assertEqual(len(items), 2))
 
-        d.addCallback(lambda dummy: self.psclient.publish(SERVICE, OTHER_NODE_ID, [ITEM_2]))
-        d.addCallback(lambda dummy: self.psclient.publish(SERVICE, COMMENTS_NODE_ID_2, [COMMENT_1, COMMENT_2]))
+        d.addCallback(
+            lambda dummy: self.psclient.publish(SERVICE, OTHER_NODE_ID, [ITEM_2])
+        )
+        d.addCallback(
+            lambda dummy: self.psclient.publish(
+                SERVICE, COMMENTS_NODE_ID_2, [COMMENT_1, COMMENT_2]
+            )
+        )
         d.addCallback(lambda dummy: self.psclient.items(SERVICE, OTHER_NODE_ID))
         d.addCallback(lambda items: self.assertEqual(len(items), 1))
         d.addCallback(lambda dummy: self.psclient.items(SERVICE, COMMENTS_NODE_ID_2))
         d.addCallback(lambda items: self.assertEqual(len(items), 2))
 
         def clean(res):
-            del self.host.plugins['XEP-0060'].node_cache[C.PROFILE[0] + '@found@' + SERVICE]
+            del self.host.plugins["XEP-0060"].node_cache[
+                C.PROFILE[0] + "@found@" + SERVICE
+            ]
             return res
 
         d.addCallback(lambda dummy: self.plugin.deleteAllGroupBlogs(C.PROFILE[0]))
@@ -353,21 +521,33 @@
         self._initialise(C.PROFILE[0])
         self.host.profiles[C.PROFILE[0]].roster.addItem(jid.JID(OTHER_PUBLISHER))
         d = self.psclient.publish(SERVICE, NODE_ID, [ITEM_1])
-        d.addCallback(lambda dummy: self.psclient.publish(SERVICE, COMMENTS_NODE_ID_1, [COMMENT_1, COMMENT_2]))
+        d.addCallback(
+            lambda dummy: self.psclient.publish(
+                SERVICE, COMMENTS_NODE_ID_1, [COMMENT_1, COMMENT_2]
+            )
+        )
         d.addCallback(lambda dummy: self.psclient.items(SERVICE, NODE_ID))
         d.addCallback(lambda items: self.assertEqual(len(items), 1))
         d.addCallback(lambda dummy: self.psclient.items(SERVICE, COMMENTS_NODE_ID_1))
         d.addCallback(lambda items: self.assertEqual(len(items), 2))
 
-        d.addCallback(lambda dummy: self.psclient.publish(SERVICE, OTHER_NODE_ID, [ITEM_2]))
-        d.addCallback(lambda dummy: self.psclient.publish(SERVICE, COMMENTS_NODE_ID_2, [COMMENT_1, COMMENT_2]))
+        d.addCallback(
+            lambda dummy: self.psclient.publish(SERVICE, OTHER_NODE_ID, [ITEM_2])
+        )
+        d.addCallback(
+            lambda dummy: self.psclient.publish(
+                SERVICE, COMMENTS_NODE_ID_2, [COMMENT_1, COMMENT_2]
+            )
+        )
         d.addCallback(lambda dummy: self.psclient.items(SERVICE, OTHER_NODE_ID))
         d.addCallback(lambda items: self.assertEqual(len(items), 1))
         d.addCallback(lambda dummy: self.psclient.items(SERVICE, COMMENTS_NODE_ID_2))
         d.addCallback(lambda items: self.assertEqual(len(items), 2))
 
         def clean(res):
-            del self.host.plugins['XEP-0060'].node_cache[C.PROFILE[0] + '@found@' + SERVICE]
+            del self.host.plugins["XEP-0060"].node_cache[
+                C.PROFILE[0] + "@found@" + SERVICE
+            ]
             return res
 
         d.addCallback(lambda dummy: self.plugin.deleteAllGroupBlogsComments(C.PROFILE[0]))
@@ -388,24 +568,38 @@
         self._initialise(C.PROFILE[0])
         self.host.profiles[C.PROFILE[0]].roster.addItem(jid.JID(OTHER_PUBLISHER))
         d = self.psclient.publish(SERVICE, NODE_ID, [ITEM_1])
-        d.addCallback(lambda dummy: self.psclient.publish(SERVICE, COMMENTS_NODE_ID_1, [COMMENT_1, COMMENT_2]))
+        d.addCallback(
+            lambda dummy: self.psclient.publish(
+                SERVICE, COMMENTS_NODE_ID_1, [COMMENT_1, COMMENT_2]
+            )
+        )
         d.addCallback(lambda dummy: self.psclient.items(SERVICE, NODE_ID))
         d.addCallback(lambda items: self.assertEqual(len(items), 1))
         d.addCallback(lambda dummy: self.psclient.items(SERVICE, COMMENTS_NODE_ID_1))
         d.addCallback(lambda items: self.assertEqual(len(items), 2))
 
-        d.addCallback(lambda dummy: self.psclient.publish(SERVICE, OTHER_NODE_ID, [ITEM_2]))
-        d.addCallback(lambda dummy: self.psclient.publish(SERVICE, COMMENTS_NODE_ID_2, [COMMENT_1, COMMENT_2]))
+        d.addCallback(
+            lambda dummy: self.psclient.publish(SERVICE, OTHER_NODE_ID, [ITEM_2])
+        )
+        d.addCallback(
+            lambda dummy: self.psclient.publish(
+                SERVICE, COMMENTS_NODE_ID_2, [COMMENT_1, COMMENT_2]
+            )
+        )
         d.addCallback(lambda dummy: self.psclient.items(SERVICE, OTHER_NODE_ID))
         d.addCallback(lambda items: self.assertEqual(len(items), 1))
         d.addCallback(lambda dummy: self.psclient.items(SERVICE, COMMENTS_NODE_ID_2))
         d.addCallback(lambda items: self.assertEqual(len(items), 2))
 
         def clean(res):
-            del self.host.plugins['XEP-0060'].node_cache[C.PROFILE[0] + '@found@' + SERVICE]
+            del self.host.plugins["XEP-0060"].node_cache[
+                C.PROFILE[0] + "@found@" + SERVICE
+            ]
             return res
 
-        d.addCallback(lambda dummy: self.plugin.deleteAllGroupBlogsAndComments(C.PROFILE[0]))
+        d.addCallback(
+            lambda dummy: self.plugin.deleteAllGroupBlogsAndComments(C.PROFILE[0])
+        )
         d.addCallback(clean)
 
         d.addCallback(lambda dummy: self.psclient.items(SERVICE, NODE_ID))
--- a/sat/test/test_plugin_misc_radiocol.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/test/test_plugin_misc_radiocol.py	Wed Jun 27 20:14:46 2018 +0200
@@ -39,7 +39,9 @@
     from mutagen.easyid3 import EasyID3
     from mutagen.id3 import ID3NoHeaderError
 except ImportError:
-    raise exceptions.MissingModule(u"Missing module Mutagen, please download/install from https://bitbucket.org/lazka/mutagen")
+    raise exceptions.MissingModule(
+        u"Missing module Mutagen, please download/install from https://bitbucket.org/lazka/mutagen"
+    )
 
 import uuid
 import os
@@ -49,35 +51,38 @@
 
 ROOM_JID = JID(Const.MUC_STR[0])
 PROFILE = Const.PROFILE[0]
-REFEREE_FULL = JID(ROOM_JID.userhost() + '/' + Const.JID[0].user)
+REFEREE_FULL = JID(ROOM_JID.userhost() + "/" + Const.JID[0].user)
 PLAYERS_INDICES = [0, 1, 3]  # referee included
 OTHER_PROFILES = [Const.PROFILE[1], Const.PROFILE[3]]
 OTHER_PLAYERS = [Const.JID[1], Const.JID[3]]
 
 
 class RadiocolTest(helpers.SatTestCase):
-
     def setUp(self):
         self.host = helpers.FakeSAT()
 
     def reinit(self):
         self.host.reinit()
-        self.host.plugins['ROOM-GAME'] = plugin_room_game.RoomGame(self.host)
+        self.host.plugins["ROOM-GAME"] = plugin_room_game.RoomGame(self.host)
         self.plugin = plugin.Radiocol(self.host)  # must be init after ROOM-GAME
         self.plugin.testing = True
-        self.plugin_0045 = self.host.plugins['XEP-0045'] = helpers_plugins.FakeXEP_0045(self.host)
-        self.plugin_0249 = self.host.plugins['XEP-0249'] = helpers_plugins.FakeXEP_0249(self.host)
+        self.plugin_0045 = self.host.plugins["XEP-0045"] = helpers_plugins.FakeXEP_0045(
+            self.host
+        )
+        self.plugin_0249 = self.host.plugins["XEP-0249"] = helpers_plugins.FakeXEP_0249(
+            self.host
+        )
         for profile in Const.PROFILE:
             self.host.getClient(profile)  # init self.host.profiles[profile]
         self.songs = []
         self.playlist = []
-        self.sound_dir = self.host.memory.getConfig('', 'media_dir') + '/test/sound/'
+        self.sound_dir = self.host.memory.getConfig("", "media_dir") + "/test/sound/"
         try:
             for filename in os.listdir(self.sound_dir):
-                if filename.endswith('.ogg') or filename.endswith('.mp3'):
+                if filename.endswith(".ogg") or filename.endswith(".mp3"):
                     self.songs.append(filename)
         except OSError:
-            raise SkipTest('The sound samples in sat_media/test/sound were not found')
+            raise SkipTest("The sound samples in sat_media/test/sound were not found")
 
     def _buildPlayers(self, players=[]):
         """@return: the "started" content built with the given players"""
@@ -101,10 +106,17 @@
         if isinstance(content, list):
             new_content = copy.deepcopy(content)
             for element in new_content:
-                if not element.hasAttribute('xmlns'):
-                    element['xmlns'] = ''
+                if not element.hasAttribute("xmlns"):
+                    element["xmlns"] = ""
             content = "".join([element.toXml() for element in new_content])
-        return "<message to='%s' type='%s'><%s xmlns='%s'>%s</%s></message>" % (to_jid.full(), type_, plugin.RADIOC_TAG, plugin.NC_RADIOCOL, content, plugin.RADIOC_TAG)
+        return "<message to='%s' type='%s'><%s xmlns='%s'>%s</%s></message>" % (
+            to_jid.full(),
+            type_,
+            plugin.RADIOC_TAG,
+            plugin.NC_RADIOCOL,
+            content,
+            plugin.RADIOC_TAG,
+        )
 
     def _rejectSongCb(self, profile_index):
         """Check if the message "song_rejected" has been sent by the referee
@@ -112,24 +124,41 @@
         @param profile_index: uploader's profile"""
         sent = self.host.getSentMessage(0)
         content = "<song_rejected xmlns='' reason='Too many songs in queue'/>"
-        self.assertEqualXML(sent.toXml(), self._expectedMessage(JID(ROOM_JID.userhost() + '/' + self.plugin_0045.getNick(0, profile_index), 'normal', content)))
-        self._roomGameCmd(sent, ['radiocolSongRejected', ROOM_JID.full(), 'Too many songs in queue'])
+        self.assertEqualXML(
+            sent.toXml(),
+            self._expectedMessage(
+                JID(
+                    ROOM_JID.userhost()
+                    + "/"
+                    + self.plugin_0045.getNick(0, profile_index),
+                    "normal",
+                    content,
+                )
+            ),
+        )
+        self._roomGameCmd(
+            sent, ["radiocolSongRejected", ROOM_JID.full(), "Too many songs in queue"]
+        )
 
     def _noUploadCb(self):
         """Check if the message "no_upload" has been sent by the referee
         and process the command with the profiles of each room users"""
         sent = self.host.getSentMessage(0)
         content = "<no_upload xmlns=''/>"
-        self.assertEqualXML(sent.toXml(), self._expectedMessage(ROOM_JID, 'groupchat', content))
-        self._roomGameCmd(sent, ['radiocolNoUpload', ROOM_JID.full()])
+        self.assertEqualXML(
+            sent.toXml(), self._expectedMessage(ROOM_JID, "groupchat", content)
+        )
+        self._roomGameCmd(sent, ["radiocolNoUpload", ROOM_JID.full()])
 
     def _uploadOkCb(self):
         """Check if the message "upload_ok" has been sent by the referee
         and process the command with the profiles of each room users"""
         sent = self.host.getSentMessage(0)
         content = "<upload_ok xmlns=''/>"
-        self.assertEqualXML(sent.toXml(), self._expectedMessage(ROOM_JID, 'groupchat', content))
-        self._roomGameCmd(sent, ['radiocolUploadOk', ROOM_JID.full()])
+        self.assertEqualXML(
+            sent.toXml(), self._expectedMessage(ROOM_JID, "groupchat", content)
+        )
+        self._roomGameCmd(sent, ["radiocolUploadOk", ROOM_JID.full()])
 
     def _preloadCb(self, attrs, profile_index):
         """Check if the message "preload" has been sent by the referee
@@ -138,15 +167,33 @@
         @param profile_index: profile index of the uploader
         """
         sent = self.host.getSentMessage(0)
-        attrs['sender'] = self.plugin_0045.getNick(0, profile_index)
-        radiocol_elt = domish.generateElementsNamed(sent.elements(), 'radiocol').next()
-        preload_elt = domish.generateElementsNamed(radiocol_elt.elements(), 'preload').next()
-        attrs['timestamp'] = preload_elt['timestamp']  # we could not guess it...
-        content = "<preload xmlns='' %s/>" % " ".join(["%s='%s'" % (attr, attrs[attr]) for attr in attrs])
-        if sent.hasAttribute('from'):
-            del sent['from']
-        self.assertEqualXML(sent.toXml(), self._expectedMessage(ROOM_JID, 'groupchat', content))
-        self._roomGameCmd(sent, ['radiocolPreload', ROOM_JID.full(), attrs['timestamp'], attrs['filename'], attrs['title'], attrs['artist'], attrs['album'], attrs['sender']])
+        attrs["sender"] = self.plugin_0045.getNick(0, profile_index)
+        radiocol_elt = domish.generateElementsNamed(sent.elements(), "radiocol").next()
+        preload_elt = domish.generateElementsNamed(
+            radiocol_elt.elements(), "preload"
+        ).next()
+        attrs["timestamp"] = preload_elt["timestamp"]  # we could not guess it...
+        content = "<preload xmlns='' %s/>" % " ".join(
+            ["%s='%s'" % (attr, attrs[attr]) for attr in attrs]
+        )
+        if sent.hasAttribute("from"):
+            del sent["from"]
+        self.assertEqualXML(
+            sent.toXml(), self._expectedMessage(ROOM_JID, "groupchat", content)
+        )
+        self._roomGameCmd(
+            sent,
+            [
+                "radiocolPreload",
+                ROOM_JID.full(),
+                attrs["timestamp"],
+                attrs["filename"],
+                attrs["title"],
+                attrs["artist"],
+                attrs["album"],
+                attrs["sender"],
+            ],
+        )
 
     def _playNextSongCb(self):
         """Check if the message "play" has been sent by the referee
@@ -154,11 +201,13 @@
         sent = self.host.getSentMessage(0)
         filename = self.playlist.pop(0)
         content = "<play xmlns='' filename='%s' />" % filename
-        self.assertEqualXML(sent.toXml(), self._expectedMessage(ROOM_JID, 'groupchat', content))
-        self._roomGameCmd(sent, ['radiocolPlay', ROOM_JID.full(), filename])
+        self.assertEqualXML(
+            sent.toXml(), self._expectedMessage(ROOM_JID, "groupchat", content)
+        )
+        self._roomGameCmd(sent, ["radiocolPlay", ROOM_JID.full(), filename])
 
         game_data = self.plugin.games[ROOM_JID]
-        if len(game_data['queue']) == plugin.QUEUE_LIMIT - 1:
+        if len(game_data["queue"]) == plugin.QUEUE_LIMIT - 1:
             self._uploadOkCb()
 
     def _addSongCb(self, d, filepath, profile_index):
@@ -174,7 +223,7 @@
         game_data = self.plugin.games[ROOM_JID]
 
         # this is copied from the plugin
-        if filepath.lower().endswith('.mp3'):
+        if filepath.lower().endswith(".mp3"):
             actual_song = MP3(filepath)
             try:
                 song = EasyID3(filepath)
@@ -182,27 +231,36 @@
                 class Info(object):
                     def __init__(self, length):
                         self.length = length
+
                 song.info = Info(actual_song.info.length)
             except ID3NoHeaderError:
                 song = actual_song
         else:
             song = OggVorbis(filepath)
 
-        attrs = {'filename': os.path.basename(filepath),
-                 'title': song.get("title", ["Unknown"])[0],
-                 'artist': song.get("artist", ["Unknown"])[0],
-                 'album': song.get("album", ["Unknown"])[0],
-                 'length': str(song.info.length)
-                 }
-        self.assertEqual(game_data['to_delete'][attrs['filename']], filepath)
+        attrs = {
+            "filename": os.path.basename(filepath),
+            "title": song.get("title", ["Unknown"])[0],
+            "artist": song.get("artist", ["Unknown"])[0],
+            "album": song.get("album", ["Unknown"])[0],
+            "length": str(song.info.length),
+        }
+        self.assertEqual(game_data["to_delete"][attrs["filename"]], filepath)
 
-        content = "<song_added xmlns='' %s/>" % " ".join(["%s='%s'" % (attr, attrs[attr]) for attr in attrs])
+        content = "<song_added xmlns='' %s/>" % " ".join(
+            ["%s='%s'" % (attr, attrs[attr]) for attr in attrs]
+        )
         sent = self.host.getSentMessage(profile_index)
-        self.assertEqualXML(sent.toXml(), self._expectedMessage(REFEREE_FULL, 'normal', content))
+        self.assertEqualXML(
+            sent.toXml(), self._expectedMessage(REFEREE_FULL, "normal", content)
+        )
 
-        reject_song = len(game_data['queue']) >= plugin.QUEUE_LIMIT
-        no_upload = len(game_data['queue']) + 1 >= plugin.QUEUE_LIMIT
-        play_next = not game_data['playing'] and len(game_data['queue']) + 1 == plugin.QUEUE_TO_START
+        reject_song = len(game_data["queue"]) >= plugin.QUEUE_LIMIT
+        no_upload = len(game_data["queue"]) + 1 >= plugin.QUEUE_LIMIT
+        play_next = (
+            not game_data["playing"]
+            and len(game_data["queue"]) + 1 == plugin.QUEUE_TO_START
+        )
 
         self._roomGameCmd(sent, profile_index)  # queue unchanged or +1
         if reject_song:
@@ -211,7 +269,7 @@
         if no_upload:
             self._noUploadCb()
         self._preloadCb(attrs, profile_index)
-        self.playlist.append(attrs['filename'])
+        self.playlist.append(attrs["filename"])
         if play_next:
             self._playNextSongCb()  # queue -1
 
@@ -229,21 +287,28 @@
             call = from_index
             from_index = 0
 
-        sent['from'] = ROOM_JID.full() + '/' + self.plugin_0045.getNick(0, from_index)
-        recipient = JID(sent['to']).resource
+        sent["from"] = ROOM_JID.full() + "/" + self.plugin_0045.getNick(0, from_index)
+        recipient = JID(sent["to"]).resource
 
         # The message could have been sent to a room user (room_jid + '/' + nick),
         # but when it is received, the 'to' attribute of the message has been
         # changed to the recipient own JID. We need to simulate that here.
         if recipient:
             room = self.plugin_0045.getRoom(0, 0)
-            sent['to'] = Const.JID_STR[0] if recipient == room.nick else room.roster[recipient].entity.full()
+            sent["to"] = (
+                Const.JID_STR[0]
+                if recipient == room.nick
+                else room.roster[recipient].entity.full()
+            )
 
         for index in xrange(0, len(Const.PROFILE)):
             nick = self.plugin_0045.getNick(0, index)
             if nick:
                 if not recipient or nick == recipient:
-                    if call and (self.plugin.isPlayer(ROOM_JID, nick) or call[0] == 'radiocolStarted'):
+                    if call and (
+                        self.plugin.isPlayer(ROOM_JID, nick)
+                        or call[0] == "radiocolStarted"
+                    ):
                         args = copy.deepcopy(call)
                         args.append(Const.PROFILE[index])
                         self.host.bridge.expectCall(*args)
@@ -255,16 +320,35 @@
         @param profile_index: index of the profile to be synchronized
         """
         for nick in sync_data:
-            expected = self._expectedMessage(JID(ROOM_JID.userhost() + '/' + nick), 'normal', sync_data[nick])
+            expected = self._expectedMessage(
+                JID(ROOM_JID.userhost() + "/" + nick), "normal", sync_data[nick]
+            )
             sent = self.host.getSentMessage(0)
             self.assertEqualXML(sent.toXml(), expected)
             for elt in sync_data[nick]:
-                if elt.name == 'preload':
-                    self.host.bridge.expectCall('radiocolPreload', ROOM_JID.full(), elt['timestamp'], elt['filename'], elt['title'], elt['artist'], elt['album'], elt['sender'], Const.PROFILE[profile_index])
-                elif elt.name == 'play':
-                    self.host.bridge.expectCall('radiocolPlay', ROOM_JID.full(), elt['filename'], Const.PROFILE[profile_index])
-                elif elt.name == 'no_upload':
-                    self.host.bridge.expectCall('radiocolNoUpload', ROOM_JID.full(), Const.PROFILE[profile_index])
+                if elt.name == "preload":
+                    self.host.bridge.expectCall(
+                        "radiocolPreload",
+                        ROOM_JID.full(),
+                        elt["timestamp"],
+                        elt["filename"],
+                        elt["title"],
+                        elt["artist"],
+                        elt["album"],
+                        elt["sender"],
+                        Const.PROFILE[profile_index],
+                    )
+                elif elt.name == "play":
+                    self.host.bridge.expectCall(
+                        "radiocolPlay",
+                        ROOM_JID.full(),
+                        elt["filename"],
+                        Const.PROFILE[profile_index],
+                    )
+                elif elt.name == "no_upload":
+                    self.host.bridge.expectCall(
+                        "radiocolNoUpload", ROOM_JID.full(), Const.PROFILE[profile_index]
+                    )
             sync_data[nick]
             self._roomGameCmd(sent, [])
 
@@ -280,12 +364,12 @@
         if player_index not in PLAYERS_INDICES:
             # this user is actually not a player
             self.assertFalse(self.plugin.isPlayer(ROOM_JID, user_nick))
-            to_jid, type_ = (JID(ROOM_JID.userhost() + '/' + user_nick), 'normal')
+            to_jid, type_ = (JID(ROOM_JID.userhost() + "/" + user_nick), "normal")
         else:
             # this user is a player
             self.assertTrue(self.plugin.isPlayer(ROOM_JID, user_nick))
             nicks.append(user_nick)
-            to_jid, type_ = (ROOM_JID, 'groupchat')
+            to_jid, type_ = (ROOM_JID, "groupchat")
 
         # Check that the message "players" has been sent by the referee
         expected = self._expectedMessage(to_jid, type_, self._buildPlayers(nicks))
@@ -293,7 +377,16 @@
         self.assertEqualXML(sent.toXml(), expected)
 
         # Process the command with the profiles of each room users
-        self._roomGameCmd(sent, ['radiocolStarted', ROOM_JID.full(), REFEREE_FULL.full(), nicks, [plugin.QUEUE_TO_START, plugin.QUEUE_LIMIT]])
+        self._roomGameCmd(
+            sent,
+            [
+                "radiocolStarted",
+                ROOM_JID.full(),
+                REFEREE_FULL.full(),
+                nicks,
+                [plugin.QUEUE_TO_START, plugin.QUEUE_LIMIT],
+            ],
+        )
 
         if sync:
             self._syncCb(self.plugin._getSyncData(ROOM_JID, [user_nick]), player_index)
@@ -322,12 +415,14 @@
         else:
             song_index = song_index % len(self.songs)
             src_filename = self.songs[song_index]
-            dst_filepath = '/tmp/%s%s' % (uuid.uuid1(), os.path.splitext(src_filename)[1])
+            dst_filepath = "/tmp/%s%s" % (uuid.uuid1(), os.path.splitext(src_filename)[1])
             shutil.copy(self.sound_dir + src_filename, dst_filepath)
             expect_io_error = False
 
         try:
-            d = self.plugin.radiocolSongAdded(REFEREE_FULL, dst_filepath, Const.PROFILE[profile_index])
+            d = self.plugin.radiocolSongAdded(
+                REFEREE_FULL, dst_filepath, Const.PROFILE[profile_index]
+            )
         except IOError:
             self.assertTrue(expect_io_error)
             return
@@ -340,7 +435,7 @@
                 self.fail("Adding a song which is not OGG nor MP3 should fail!")
             self.assertEqual(failure.value.__class__, exceptions.DataError)
 
-        if src_filename.endswith('.ogg') or src_filename.endswith('.mp3'):
+        if src_filename.endswith(".ogg") or src_filename.endswith(".mp3"):
             d.addCallbacks(cb, cb)
         else:
             d.addCallbacks(eb, eb)
@@ -362,14 +457,28 @@
         nicks = [self.plugin_0045.getNick(0, 0)]
 
         sent = self.host.getSentMessage(0)
-        self.assertEqualXML(sent.toXml(), self._expectedMessage(ROOM_JID, 'groupchat', self._buildPlayers(nicks)))
-        self._roomGameCmd(sent, ['radiocolStarted', ROOM_JID.full(), REFEREE_FULL.full(), nicks, [plugin.QUEUE_TO_START, plugin.QUEUE_LIMIT]])
+        self.assertEqualXML(
+            sent.toXml(),
+            self._expectedMessage(ROOM_JID, "groupchat", self._buildPlayers(nicks)),
+        )
+        self._roomGameCmd(
+            sent,
+            [
+                "radiocolStarted",
+                ROOM_JID.full(),
+                REFEREE_FULL.full(),
+                nicks,
+                [plugin.QUEUE_TO_START, plugin.QUEUE_LIMIT],
+            ],
+        )
 
         self._joinRoom(room, nicks, 1)  # player joins
         self._joinRoom(room, nicks, 4)  # user not playing joins
 
         song_index = 0
-        self._uploadSong(song_index, 0)  # ogg or mp3 file should exist in sat_media/test/song
+        self._uploadSong(
+            song_index, 0
+        )  # ogg or mp3 file should exist in sat_media/test/song
         self._uploadSong(None, 0)  # non existing file
 
         # another songs are added by Const.JID[1] until the radio starts + 1 to fill the queue
@@ -379,7 +488,9 @@
 
         self.plugin.playNext(Const.MUC[0], PROFILE)  # simulate the end of the first song
         self._playNextSongCb()
-        self._uploadSong(song_index, 1)  # now the song is accepted and the queue is full again
+        self._uploadSong(
+            song_index, 1
+        )  # now the song is accepted and the queue is full again
 
         self._joinRoom(room, nicks, 3)  # new player joins
 
@@ -396,7 +507,7 @@
         self._playNextSongCb()
 
         for filename in self.playlist:
-            self.plugin.deleteFile('/tmp/' + filename)
+            self.plugin.deleteFile("/tmp/" + filename)
 
         return defer.succeed(None)
 
--- a/sat/test/test_plugin_misc_room_game.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/test/test_plugin_misc_room_game.py	Wed Jun 27 20:14:46 2018 +0200
@@ -30,8 +30,8 @@
 from logging import WARNING
 
 # Data used for test initialization
-NAMESERVICE = 'http://www.goffi.org/protocol/dummy'
-TAG = 'dummy'
+NAMESERVICE = "http://www.goffi.org/protocol/dummy"
+TAG = "dummy"
 PLUGIN_INFO = {
     "name": "Dummy plugin",
     "import_name": "DUMMY",
@@ -40,7 +40,7 @@
     "dependencies": [],
     "main": "Dummy",
     "handler": "no",  # handler MUST be "no" (dynamic inheritance)
-    "description": _("""Dummy plugin to test room game""")
+    "description": _("""Dummy plugin to test room game"""),
 }
 
 ROOM_JID = JID(Const.MUC_STR[0])
@@ -49,16 +49,21 @@
 
 
 class RoomGameTest(helpers.SatTestCase):
-
     def setUp(self):
         self.host = helpers.FakeSAT()
 
     def reinit(self, game_init={}, player_init={}):
         self.host.reinit()
         self.plugin = plugin.RoomGame(self.host)
-        self.plugin._init_(self.host, PLUGIN_INFO, (NAMESERVICE, TAG), game_init, player_init)
-        self.plugin_0045 = self.host.plugins['XEP-0045'] = helpers_plugins.FakeXEP_0045(self.host)
-        self.plugin_0249 = self.host.plugins['XEP-0249'] = helpers_plugins.FakeXEP_0249(self.host)
+        self.plugin._init_(
+            self.host, PLUGIN_INFO, (NAMESERVICE, TAG), game_init, player_init
+        )
+        self.plugin_0045 = self.host.plugins["XEP-0045"] = helpers_plugins.FakeXEP_0045(
+            self.host
+        )
+        self.plugin_0249 = self.host.plugins["XEP-0249"] = helpers_plugins.FakeXEP_0249(
+            self.host
+        )
         for profile in Const.PROFILE:
             self.host.getClient(profile)  # init self.host.profiles[profile]
 
@@ -75,7 +80,13 @@
             for i in xrange(0, len(players)):
                 content += "<player index='%s'>%s</player>" % (i, players[i])
             content += "</%s>" % tag
-        return "<message to='%s' type='%s'><%s xmlns='%s'>%s</dummy></message>" % (to.full(), type_, TAG, NAMESERVICE, content)
+        return "<message to='%s' type='%s'><%s xmlns='%s'>%s</dummy></message>" % (
+            to.full(),
+            type_,
+            TAG,
+            NAMESERVICE,
+            content,
+        )
 
     def test_createOrInvite_solo(self):
         self.reinit()
@@ -87,14 +98,18 @@
         self.reinit()
         self.plugin_0045.joinRoom(0, 0)
         other_players = [Const.JID[1], Const.JID[2]]
-        self.plugin._createOrInvite(self.plugin_0045.getRoom(0, 0), other_players, Const.PROFILE[0])
+        self.plugin._createOrInvite(
+            self.plugin_0045.getRoom(0, 0), other_players, Const.PROFILE[0]
+        )
         self.assertTrue(self.plugin._gameExists(ROOM_JID, True))
 
     def test_createOrInvite_multi_waiting(self):
-        self.reinit(player_init={'score': 0})
+        self.reinit(player_init={"score": 0})
         self.plugin_0045.joinRoom(0, 0)
         other_players = [Const.JID[1], Const.JID[2]]
-        self.plugin._createOrInvite(self.plugin_0045.getRoom(0, 0), other_players, Const.PROFILE[0])
+        self.plugin._createOrInvite(
+            self.plugin_0045.getRoom(0, 0), other_players, Const.PROFILE[0]
+        )
         self.assertTrue(self.plugin._gameExists(ROOM_JID, False))
         self.assertFalse(self.plugin._gameExists(ROOM_JID, True))
 
@@ -102,11 +117,13 @@
         self.reinit()
         self.initGame(0, 0)
         self.assertTrue(self.plugin.isReferee(ROOM_JID, Const.JID[0].user))
-        self.assertEqual([], self.plugin.games[ROOM_JID]['players'])
+        self.assertEqual([], self.plugin.games[ROOM_JID]["players"])
 
     def test_checkJoinAuth(self):
         self.reinit()
-        check = lambda value: getattr(self, "assert%s" % value)(self.plugin._checkJoinAuth(ROOM_JID, Const.JID[0], Const.JID[0].user))
+        check = lambda value: getattr(self, "assert%s" % value)(
+            self.plugin._checkJoinAuth(ROOM_JID, Const.JID[0], Const.JID[0].user)
+        )
         check(False)
         # to test the "invited" mode, the referee must be different than the user to test
         self.initGame(0, 1)
@@ -118,41 +135,66 @@
         check(True)
         self.plugin.join_mode = self.plugin.NONE
         check(False)
-        self.plugin.games[ROOM_JID]['players'].append(Const.JID[0].user)
+        self.plugin.games[ROOM_JID]["players"].append(Const.JID[0].user)
         check(True)
 
     def test_updatePlayers(self):
         self.reinit()
         self.initGame(0, 0)
-        self.assertEqual(self.plugin.games[ROOM_JID]['players'], [])
+        self.assertEqual(self.plugin.games[ROOM_JID]["players"], [])
         self.plugin._updatePlayers(ROOM_JID, [], True, Const.PROFILE[0])
-        self.assertEqual(self.plugin.games[ROOM_JID]['players'], [])
+        self.assertEqual(self.plugin.games[ROOM_JID]["players"], [])
         self.plugin._updatePlayers(ROOM_JID, ["user1"], True, Const.PROFILE[0])
-        self.assertEqual(self.plugin.games[ROOM_JID]['players'], ["user1"])
+        self.assertEqual(self.plugin.games[ROOM_JID]["players"], ["user1"])
         self.plugin._updatePlayers(ROOM_JID, ["user2", "user3"], True, Const.PROFILE[0])
-        self.assertEqual(self.plugin.games[ROOM_JID]['players'], ["user1", "user2", "user3"])
-        self.plugin._updatePlayers(ROOM_JID, ["user2", "user3"], True, Const.PROFILE[0])  # should not be stored twice
-        self.assertEqual(self.plugin.games[ROOM_JID]['players'], ["user1", "user2", "user3"])
+        self.assertEqual(
+            self.plugin.games[ROOM_JID]["players"], ["user1", "user2", "user3"]
+        )
+        self.plugin._updatePlayers(
+            ROOM_JID, ["user2", "user3"], True, Const.PROFILE[0]
+        )  # should not be stored twice
+        self.assertEqual(
+            self.plugin.games[ROOM_JID]["players"], ["user1", "user2", "user3"]
+        )
 
     def test_synchronizeRoom(self):
         self.reinit()
         self.initGame(0, 0)
         self.plugin._synchronizeRoom(ROOM_JID, [Const.MUC[0]], Const.PROFILE[0])
-        self.assertEqual(self.host.getSentMessageXml(0), self._expectedMessage(ROOM_JID, "groupchat", "players", []))
-        self.plugin.games[ROOM_JID]['players'].append("test1")
+        self.assertEqual(
+            self.host.getSentMessageXml(0),
+            self._expectedMessage(ROOM_JID, "groupchat", "players", []),
+        )
+        self.plugin.games[ROOM_JID]["players"].append("test1")
         self.plugin._synchronizeRoom(ROOM_JID, [Const.MUC[0]], Const.PROFILE[0])
-        self.assertEqual(self.host.getSentMessageXml(0), self._expectedMessage(ROOM_JID, "groupchat", "players", ["test1"]))
-        self.plugin.games[ROOM_JID]['started'] = True
-        self.plugin.games[ROOM_JID]['players'].append("test2")
+        self.assertEqual(
+            self.host.getSentMessageXml(0),
+            self._expectedMessage(ROOM_JID, "groupchat", "players", ["test1"]),
+        )
+        self.plugin.games[ROOM_JID]["started"] = True
+        self.plugin.games[ROOM_JID]["players"].append("test2")
         self.plugin._synchronizeRoom(ROOM_JID, [Const.MUC[0]], Const.PROFILE[0])
-        self.assertEqual(self.host.getSentMessageXml(0), self._expectedMessage(ROOM_JID, "groupchat", "started", ["test1", "test2"]))
-        self.plugin.games[ROOM_JID]['players'].append("test3")
-        self.plugin.games[ROOM_JID]['players'].append("test4")
+        self.assertEqual(
+            self.host.getSentMessageXml(0),
+            self._expectedMessage(ROOM_JID, "groupchat", "started", ["test1", "test2"]),
+        )
+        self.plugin.games[ROOM_JID]["players"].append("test3")
+        self.plugin.games[ROOM_JID]["players"].append("test4")
         user1 = JID(ROOM_JID.userhost() + "/" + Const.JID[0].user)
         user2 = JID(ROOM_JID.userhost() + "/" + Const.JID[1].user)
         self.plugin._synchronizeRoom(ROOM_JID, [user1, user2], Const.PROFILE[0])
-        self.assertEqualXML(self.host.getSentMessageXml(0), self._expectedMessage(user1, "normal", "started", ["test1", "test2", "test3", "test4"]))
-        self.assertEqualXML(self.host.getSentMessageXml(0), self._expectedMessage(user2, "normal", "started", ["test1", "test2", "test3", "test4"]))
+        self.assertEqualXML(
+            self.host.getSentMessageXml(0),
+            self._expectedMessage(
+                user1, "normal", "started", ["test1", "test2", "test3", "test4"]
+            ),
+        )
+        self.assertEqualXML(
+            self.host.getSentMessageXml(0),
+            self._expectedMessage(
+                user2, "normal", "started", ["test1", "test2", "test3", "test4"]
+            ),
+        )
 
     def test_invitePlayers(self):
         self.reinit()
@@ -160,25 +202,41 @@
         self.plugin_0045.joinRoom(0, 1)
         self.assertEqual(self.plugin.invitations[ROOM_JID], [])
         room = self.plugin_0045.getRoom(0, 0)
-        nicks = self.plugin._invitePlayers(room, [Const.JID[1], Const.JID[2]], Const.JID[0].user, Const.PROFILE[0])
-        self.assertEqual(self.plugin.invitations[ROOM_JID][0][1], [Const.JID[1].userhostJID(), Const.JID[2].userhostJID()])
+        nicks = self.plugin._invitePlayers(
+            room, [Const.JID[1], Const.JID[2]], Const.JID[0].user, Const.PROFILE[0]
+        )
+        self.assertEqual(
+            self.plugin.invitations[ROOM_JID][0][1],
+            [Const.JID[1].userhostJID(), Const.JID[2].userhostJID()],
+        )
         # the following assertion is True because Const.JID[1] and Const.JID[2] have the same userhost
         self.assertEqual(nicks, [Const.JID[1].user, Const.JID[2].user])
 
-        nicks = self.plugin._invitePlayers(room, [Const.JID[1], Const.JID[3]], Const.JID[0].user, Const.PROFILE[0])
-        self.assertEqual(self.plugin.invitations[ROOM_JID][1][1], [Const.JID[1].userhostJID(), Const.JID[3].userhostJID()])
+        nicks = self.plugin._invitePlayers(
+            room, [Const.JID[1], Const.JID[3]], Const.JID[0].user, Const.PROFILE[0]
+        )
+        self.assertEqual(
+            self.plugin.invitations[ROOM_JID][1][1],
+            [Const.JID[1].userhostJID(), Const.JID[3].userhostJID()],
+        )
         # this time Const.JID[1] and Const.JID[3] have the same user but the host differs
         self.assertEqual(nicks, [Const.JID[1].user])
 
     def test_checkInviteAuth(self):
-
         def check(value, index):
             nick = self.plugin_0045.getNick(0, index)
-            getattr(self, "assert%s" % value)(self.plugin._checkInviteAuth(ROOM_JID, nick))
+            getattr(self, "assert%s" % value)(
+                self.plugin._checkInviteAuth(ROOM_JID, nick)
+            )
 
         self.reinit()
 
-        for mode in [self.plugin.FROM_ALL, self.plugin.FROM_NONE, self.plugin.FROM_REFEREE, self.plugin.FROM_PLAYERS]:
+        for mode in [
+            self.plugin.FROM_ALL,
+            self.plugin.FROM_NONE,
+            self.plugin.FROM_REFEREE,
+            self.plugin.FROM_PLAYERS,
+        ]:
             self.plugin.invite_mode = mode
             check(True, 0)
 
@@ -193,7 +251,7 @@
         check(True, 0)
         check(False, 1)
         user_nick = self.plugin_0045.joinRoom(0, 1)
-        self.plugin.games[ROOM_JID]['players'].append(user_nick)
+        self.plugin.games[ROOM_JID]["players"].append(user_nick)
         self.plugin.invite_mode = self.plugin.FROM_PLAYERS
         check(True, 0)
         check(True, 1)
@@ -210,22 +268,25 @@
         self.initGame(0, 0)
         self.assertTrue(self.plugin.isPlayer(ROOM_JID, self.plugin_0045.getNick(0, 0)))
         user_nick = self.plugin_0045.joinRoom(0, 1)
-        self.plugin.games[ROOM_JID]['players'].append(user_nick)
+        self.plugin.games[ROOM_JID]["players"].append(user_nick)
         self.assertTrue(self.plugin.isPlayer(ROOM_JID, user_nick))
         self.assertFalse(self.plugin.isPlayer(ROOM_JID, self.plugin_0045.getNick(0, 2)))
 
     def test_checkWaitAuth(self):
-
         def check(value, other_players, confirmed, rest):
             room = self.plugin_0045.getRoom(0, 0)
-            self.assertEqual((value, confirmed, rest), self.plugin._checkWaitAuth(room, other_players))
+            self.assertEqual(
+                (value, confirmed, rest), self.plugin._checkWaitAuth(room, other_players)
+            )
 
         self.reinit()
         self.initGame(0, 0)
         other_players = [Const.JID[1], Const.JID[3]]
         self.plugin.wait_mode = self.plugin.FOR_NONE
         check(True, [], [], [])
-        check(True, [Const.JID[0]], [], [Const.JID[0]])  # getRoomNickOfUser checks for the other users only
+        check(
+            True, [Const.JID[0]], [], [Const.JID[0]]
+        )  # getRoomNickOfUser checks for the other users only
         check(True, other_players, [], other_players)
         self.plugin.wait_mode = self.plugin.FOR_ALL
         check(True, [], [], [])
@@ -234,28 +295,51 @@
         self.plugin_0045.joinRoom(0, 1)
         check(False, other_players, [], other_players)
         self.plugin_0045.joinRoom(0, 4)
-        check(False, other_players, [self.plugin_0045.getNickOfUser(0, 1, 0)], [Const.JID[3]])
+        check(
+            False,
+            other_players,
+            [self.plugin_0045.getNickOfUser(0, 1, 0)],
+            [Const.JID[3]],
+        )
         self.plugin_0045.joinRoom(0, 3)
-        check(True, other_players, [self.plugin_0045.getNickOfUser(0, 1, 0),
-                                    self.plugin_0045.getNickOfUser(0, 3, 0)], [])
+        check(
+            True,
+            other_players,
+            [
+                self.plugin_0045.getNickOfUser(0, 1, 0),
+                self.plugin_0045.getNickOfUser(0, 3, 0),
+            ],
+            [],
+        )
 
         other_players = [Const.JID[1], Const.JID[3], Const.JID[2]]
         # the following assertion is True because Const.JID[1] and Const.JID[2] have the same userhost
-        check(True, other_players, [self.plugin_0045.getNickOfUser(0, 1, 0),
-                                    self.plugin_0045.getNickOfUser(0, 3, 0),
-                                    self.plugin_0045.getNickOfUser(0, 2, 0)], [])
+        check(
+            True,
+            other_players,
+            [
+                self.plugin_0045.getNickOfUser(0, 1, 0),
+                self.plugin_0045.getNickOfUser(0, 3, 0),
+                self.plugin_0045.getNickOfUser(0, 2, 0),
+            ],
+            [],
+        )
 
     def test_prepareRoom_trivial(self):
         self.reinit()
         other_players = []
         self.plugin.prepareRoom(other_players, ROOM_JID, PROFILE)
         self.assertTrue(self.plugin._gameExists(ROOM_JID, True))
-        self.assertTrue(self.plugin._checkJoinAuth(ROOM_JID, Const.JID[0], Const.JID[0].user))
+        self.assertTrue(
+            self.plugin._checkJoinAuth(ROOM_JID, Const.JID[0], Const.JID[0].user)
+        )
         self.assertTrue(self.plugin._checkInviteAuth(ROOM_JID, Const.JID[0].user))
         self.assertEqual((True, [], []), self.plugin._checkWaitAuth(ROOM_JID, []))
         self.assertTrue(self.plugin.isReferee(ROOM_JID, Const.JID[0].user))
         self.assertTrue(self.plugin.isPlayer(ROOM_JID, Const.JID[0].user))
-        self.assertEqual((False, True), self.plugin._checkCreateGameAndInit(ROOM_JID, PROFILE))
+        self.assertEqual(
+            (False, True), self.plugin._checkCreateGameAndInit(ROOM_JID, PROFILE)
+        )
 
     def test_prepareRoom_invite(self):
         self.reinit()
@@ -264,10 +348,16 @@
         room = self.plugin_0045.getRoom(0, 0)
 
         self.assertTrue(self.plugin._gameExists(ROOM_JID, True))
-        self.assertTrue(self.plugin._checkJoinAuth(ROOM_JID, Const.JID[1], Const.JID[1].user))
-        self.assertFalse(self.plugin._checkJoinAuth(ROOM_JID, Const.JID[3], Const.JID[3].user))
+        self.assertTrue(
+            self.plugin._checkJoinAuth(ROOM_JID, Const.JID[1], Const.JID[1].user)
+        )
+        self.assertFalse(
+            self.plugin._checkJoinAuth(ROOM_JID, Const.JID[3], Const.JID[3].user)
+        )
         self.assertFalse(self.plugin._checkInviteAuth(ROOM_JID, Const.JID[1].user))
-        self.assertEqual((True, [], other_players), self.plugin._checkWaitAuth(room, other_players))
+        self.assertEqual(
+            (True, [], other_players), self.plugin._checkWaitAuth(room, other_players)
+        )
 
         player2_nick = self.plugin_0045.joinRoom(0, 1)
         self.plugin.userJoinedTrigger(room, room.roster[player2_nick], PROFILE)
@@ -275,21 +365,32 @@
         self.assertTrue(self.plugin._checkInviteAuth(ROOM_JID, player2_nick))
         self.assertFalse(self.plugin.isReferee(ROOM_JID, player2_nick))
         self.assertTrue(self.plugin.isPlayer(ROOM_JID, player2_nick))
-        self.assertTrue(self.plugin.isPlayer(ROOM_JID, self.plugin_0045.getNickOfUser(0, 2, 0)))
+        self.assertTrue(
+            self.plugin.isPlayer(ROOM_JID, self.plugin_0045.getNickOfUser(0, 2, 0))
+        )
         self.assertFalse(self.plugin.isPlayer(ROOM_JID, "xxx"))
-        self.assertEqual((False, False), self.plugin._checkCreateGameAndInit(ROOM_JID, Const.PROFILE[1]))
+        self.assertEqual(
+            (False, False),
+            self.plugin._checkCreateGameAndInit(ROOM_JID, Const.PROFILE[1]),
+        )
 
     def test_prepareRoom_score1(self):
-        self.reinit(player_init={'score': 0})
+        self.reinit(player_init={"score": 0})
         other_players = [Const.JID[1], Const.JID[2]]
         self.plugin.prepareRoom(other_players, ROOM_JID, PROFILE)
         room = self.plugin_0045.getRoom(0, 0)
 
         self.assertFalse(self.plugin._gameExists(ROOM_JID, True))
-        self.assertTrue(self.plugin._checkJoinAuth(ROOM_JID, Const.JID[1], Const.JID[1].user))
-        self.assertFalse(self.plugin._checkJoinAuth(ROOM_JID, Const.JID[3], Const.JID[3].user))
+        self.assertTrue(
+            self.plugin._checkJoinAuth(ROOM_JID, Const.JID[1], Const.JID[1].user)
+        )
+        self.assertFalse(
+            self.plugin._checkJoinAuth(ROOM_JID, Const.JID[3], Const.JID[3].user)
+        )
         self.assertFalse(self.plugin._checkInviteAuth(ROOM_JID, Const.JID[1].user))
-        self.assertEqual((False, [], other_players), self.plugin._checkWaitAuth(room, other_players))
+        self.assertEqual(
+            (False, [], other_players), self.plugin._checkWaitAuth(room, other_players)
+        )
 
         user_nick = self.plugin_0045.joinRoom(0, 1)
         self.plugin.userJoinedTrigger(room, room.roster[user_nick], PROFILE)
@@ -298,25 +399,33 @@
         self.assertFalse(self.plugin.isReferee(ROOM_JID, user_nick))
         self.assertTrue(self.plugin.isPlayer(ROOM_JID, user_nick))
         # the following assertion is True because Const.JID[1] and Const.JID[2] have the same userhost
-        self.assertTrue(self.plugin.isPlayer(ROOM_JID, self.plugin_0045.getNickOfUser(0, 2, 0)))
+        self.assertTrue(
+            self.plugin.isPlayer(ROOM_JID, self.plugin_0045.getNickOfUser(0, 2, 0))
+        )
         # the following assertion is True because Const.JID[1] nick in the room is equal to Const.JID[3].user
         self.assertTrue(self.plugin.isPlayer(ROOM_JID, Const.JID[3].user))
         # but Const.JID[3] is actually not in the room
         self.assertEqual(self.plugin_0045.getNickOfUser(0, 3, 0), None)
-        self.assertEqual((True, False), self.plugin._checkCreateGameAndInit(ROOM_JID, Const.PROFILE[0]))
+        self.assertEqual(
+            (True, False), self.plugin._checkCreateGameAndInit(ROOM_JID, Const.PROFILE[0])
+        )
 
     def test_prepareRoom_score2(self):
-        self.reinit(player_init={'score': 0})
+        self.reinit(player_init={"score": 0})
         other_players = [Const.JID[1], Const.JID[4]]
         self.plugin.prepareRoom(other_players, ROOM_JID, PROFILE)
         room = self.plugin_0045.getRoom(0, 0)
 
         user_nick = self.plugin_0045.joinRoom(0, 1)
         self.plugin.userJoinedTrigger(room, room.roster[user_nick], PROFILE)
-        self.assertEqual((True, False), self.plugin._checkCreateGameAndInit(ROOM_JID, PROFILE))
+        self.assertEqual(
+            (True, False), self.plugin._checkCreateGameAndInit(ROOM_JID, PROFILE)
+        )
         user_nick = self.plugin_0045.joinRoom(0, 4)
         self.plugin.userJoinedTrigger(room, room.roster[user_nick], PROFILE)
-        self.assertEqual((False, True), self.plugin._checkCreateGameAndInit(ROOM_JID, PROFILE))
+        self.assertEqual(
+            (False, True), self.plugin._checkCreateGameAndInit(ROOM_JID, PROFILE)
+        )
 
     def test_userJoinedTrigger(self):
         self.reinit(player_init={"xxx": "xyz"})
@@ -324,34 +433,50 @@
         self.plugin.prepareRoom(other_players, ROOM_JID, PROFILE)
         nicks = [self.plugin_0045.getNick(0, 0)]
 
-        self.assertEqual(self.host.getSentMessageXml(0), self._expectedMessage(ROOM_JID, "groupchat", "players", nicks))
+        self.assertEqual(
+            self.host.getSentMessageXml(0),
+            self._expectedMessage(ROOM_JID, "groupchat", "players", nicks),
+        )
         self.assertTrue(len(self.plugin.invitations[ROOM_JID]) == 1)
 
         # wrong profile
         user_nick = self.plugin_0045.joinRoom(0, 1)
         room = self.plugin_0045.getRoom(0, 1)
         self.plugin.userJoinedTrigger(room, User(user_nick, Const.JID[1]), OTHER_PROFILE)
-        self.assertEqual(self.host.getSentMessage(0), None)  # no new message has been sent
+        self.assertEqual(
+            self.host.getSentMessage(0), None
+        )  # no new message has been sent
         self.assertFalse(self.plugin._gameExists(ROOM_JID, True))  # game not started
 
         # referee profile, user is allowed, wait for one more
         room = self.plugin_0045.getRoom(0, 0)
         self.plugin.userJoinedTrigger(room, User(user_nick, Const.JID[1]), PROFILE)
         nicks.append(user_nick)
-        self.assertEqual(self.host.getSentMessageXml(0), self._expectedMessage(ROOM_JID, "groupchat", "players", nicks))
+        self.assertEqual(
+            self.host.getSentMessageXml(0),
+            self._expectedMessage(ROOM_JID, "groupchat", "players", nicks),
+        )
         self.assertFalse(self.plugin._gameExists(ROOM_JID, True))  # game not started
 
         # referee profile, user is not allowed
         user_nick = self.plugin_0045.joinRoom(0, 4)
         self.plugin.userJoinedTrigger(room, User(user_nick, Const.JID[4]), PROFILE)
-        self.assertEqual(self.host.getSentMessageXml(0), self._expectedMessage(JID(ROOM_JID.userhost() + '/' + user_nick), "normal", "players", nicks))
+        self.assertEqual(
+            self.host.getSentMessageXml(0),
+            self._expectedMessage(
+                JID(ROOM_JID.userhost() + "/" + user_nick), "normal", "players", nicks
+            ),
+        )
         self.assertFalse(self.plugin._gameExists(ROOM_JID, True))  # game not started
 
         # referee profile, user is allowed, everybody here
         user_nick = self.plugin_0045.joinRoom(0, 3)
         self.plugin.userJoinedTrigger(room, User(user_nick, Const.JID[3]), PROFILE)
         nicks.append(user_nick)
-        self.assertEqual(self.host.getSentMessageXml(0), self._expectedMessage(ROOM_JID, "groupchat", "started", nicks))
+        self.assertEqual(
+            self.host.getSentMessageXml(0),
+            self._expectedMessage(ROOM_JID, "groupchat", "started", nicks),
+        )
         self.assertTrue(self.plugin._gameExists(ROOM_JID, True))  # game started
         self.assertTrue(len(self.plugin.invitations[ROOM_JID]) == 0)
 
@@ -364,7 +489,10 @@
         user_nick = self.plugin_0045.joinRoom(0, 3)
         self.plugin.userJoinedTrigger(room, User(user_nick, Const.JID[3]), PROFILE)
         nicks.append(user_nick)
-        self.assertEqual(self.host.getSentMessageXml(0), self._expectedMessage(ROOM_JID, "groupchat", "started", nicks))
+        self.assertEqual(
+            self.host.getSentMessageXml(0),
+            self._expectedMessage(ROOM_JID, "groupchat", "started", nicks),
+        )
         self.assertTrue(self.plugin._gameExists(ROOM_JID, True))
 
     def test_userLeftTrigger(self):
@@ -373,7 +501,14 @@
         self.plugin.prepareRoom(other_players, ROOM_JID, PROFILE)
         room = self.plugin_0045.getRoom(0, 0)
         nicks = [self.plugin_0045.getNick(0, 0)]
-        self.assertEqual(self.plugin.invitations[ROOM_JID][0][1], [Const.JID[1].userhostJID(), Const.JID[3].userhostJID(), Const.JID[4].userhostJID()])
+        self.assertEqual(
+            self.plugin.invitations[ROOM_JID][0][1],
+            [
+                Const.JID[1].userhostJID(),
+                Const.JID[3].userhostJID(),
+                Const.JID[4].userhostJID(),
+            ],
+        )
 
         # one user joins
         user_nick = self.plugin_0045.joinRoom(0, 1)
@@ -381,16 +516,20 @@
         nicks.append(user_nick)
 
         # the user leaves
-        self.assertEqual(self.plugin.games[ROOM_JID]['players'], nicks)
+        self.assertEqual(self.plugin.games[ROOM_JID]["players"], nicks)
         room = self.plugin_0045.getRoom(0, 1)
         # to not call self.plugin_0045.leaveRoom(0, 1) here, we are testing the trigger with a wrong profile
-        self.plugin.userLeftTrigger(room, User(user_nick, Const.JID[1]), Const.PROFILE[1])  # not the referee
-        self.assertEqual(self.plugin.games[ROOM_JID]['players'], nicks)
+        self.plugin.userLeftTrigger(
+            room, User(user_nick, Const.JID[1]), Const.PROFILE[1]
+        )  # not the referee
+        self.assertEqual(self.plugin.games[ROOM_JID]["players"], nicks)
         room = self.plugin_0045.getRoom(0, 0)
         user_nick = self.plugin_0045.leaveRoom(0, 1)
-        self.plugin.userLeftTrigger(room, User(user_nick, Const.JID[1]), PROFILE)  # referee
+        self.plugin.userLeftTrigger(
+            room, User(user_nick, Const.JID[1]), PROFILE
+        )  # referee
         nicks.pop()
-        self.assertEqual(self.plugin.games[ROOM_JID]['players'], nicks)
+        self.assertEqual(self.plugin.games[ROOM_JID]["players"], nicks)
 
         # all the users join
         user_nick = self.plugin_0045.joinRoom(0, 1)
@@ -402,20 +541,25 @@
         user_nick = self.plugin_0045.joinRoom(0, 4)
         self.plugin.userJoinedTrigger(room, User(user_nick, Const.JID[4]), PROFILE)
         nicks.append(user_nick)
-        self.assertEqual(self.plugin.games[ROOM_JID]['players'], nicks)
+        self.assertEqual(self.plugin.games[ROOM_JID]["players"], nicks)
         self.assertTrue(len(self.plugin.invitations[ROOM_JID]) == 0)
 
         # one user leaves
         user_nick = self.plugin_0045.leaveRoom(0, 4)
         self.plugin.userLeftTrigger(room, User(user_nick, Const.JID[4]), PROFILE)
         nicks.pop()
-        self.assertEqual(self.plugin.invitations[ROOM_JID][0][1], [Const.JID[4].userhostJID()])
+        self.assertEqual(
+            self.plugin.invitations[ROOM_JID][0][1], [Const.JID[4].userhostJID()]
+        )
 
         # another leaves
         user_nick = self.plugin_0045.leaveRoom(0, 3)
         self.plugin.userLeftTrigger(room, User(user_nick, Const.JID[3]), PROFILE)
         nicks.pop()
-        self.assertEqual(self.plugin.invitations[ROOM_JID][0][1], [Const.JID[4].userhostJID(), Const.JID[3].userhostJID()])
+        self.assertEqual(
+            self.plugin.invitations[ROOM_JID][0][1],
+            [Const.JID[4].userhostJID(), Const.JID[3].userhostJID()],
+        )
 
         # they can join again
         user_nick = self.plugin_0045.joinRoom(0, 3)
@@ -424,31 +568,43 @@
         user_nick = self.plugin_0045.joinRoom(0, 4)
         self.plugin.userJoinedTrigger(room, User(user_nick, Const.JID[4]), PROFILE)
         nicks.append(user_nick)
-        self.assertEqual(self.plugin.games[ROOM_JID]['players'], nicks)
+        self.assertEqual(self.plugin.games[ROOM_JID]["players"], nicks)
         self.assertTrue(len(self.plugin.invitations[ROOM_JID]) == 0)
 
     def test__checkCreateGameAndInit(self):
         self.reinit()
         helpers.muteLogging()
-        self.assertEqual((False, False), self.plugin._checkCreateGameAndInit(ROOM_JID, PROFILE))
+        self.assertEqual(
+            (False, False), self.plugin._checkCreateGameAndInit(ROOM_JID, PROFILE)
+        )
         helpers.unmuteLogging()
 
         nick = self.plugin_0045.joinRoom(0, 0)
-        self.assertEqual((True, False), self.plugin._checkCreateGameAndInit(ROOM_JID, PROFILE))
+        self.assertEqual(
+            (True, False), self.plugin._checkCreateGameAndInit(ROOM_JID, PROFILE)
+        )
         self.assertTrue(self.plugin._gameExists(ROOM_JID, False))
         self.assertFalse(self.plugin._gameExists(ROOM_JID, True))
         self.assertTrue(self.plugin.isReferee(ROOM_JID, nick))
 
         helpers.muteLogging()
-        self.assertEqual((False, False), self.plugin._checkCreateGameAndInit(ROOM_JID, OTHER_PROFILE))
+        self.assertEqual(
+            (False, False), self.plugin._checkCreateGameAndInit(ROOM_JID, OTHER_PROFILE)
+        )
         helpers.unmuteLogging()
 
         self.plugin_0045.joinRoom(0, 1)
-        self.assertEqual((False, False), self.plugin._checkCreateGameAndInit(ROOM_JID, OTHER_PROFILE))
+        self.assertEqual(
+            (False, False), self.plugin._checkCreateGameAndInit(ROOM_JID, OTHER_PROFILE)
+        )
 
         self.plugin.createGame(ROOM_JID, [Const.JID[1]], PROFILE)
-        self.assertEqual((False, True), self.plugin._checkCreateGameAndInit(ROOM_JID, PROFILE))
-        self.assertEqual((False, False), self.plugin._checkCreateGameAndInit(ROOM_JID, OTHER_PROFILE))
+        self.assertEqual(
+            (False, True), self.plugin._checkCreateGameAndInit(ROOM_JID, PROFILE)
+        )
+        self.assertEqual(
+            (False, False), self.plugin._checkCreateGameAndInit(ROOM_JID, OTHER_PROFILE)
+        )
 
     def test_createGame(self):
 
@@ -460,27 +616,39 @@
         # game not exists
         self.plugin.createGame(ROOM_JID, nicks, PROFILE)
         self.assertTrue(self.plugin._gameExists(ROOM_JID, True))
-        self.assertEqual(self.plugin.games[ROOM_JID]['players'], nicks)
-        self.assertEqual(self.host.getSentMessageXml(0), self._expectedMessage(ROOM_JID, "groupchat", "started", nicks))
+        self.assertEqual(self.plugin.games[ROOM_JID]["players"], nicks)
+        self.assertEqual(
+            self.host.getSentMessageXml(0),
+            self._expectedMessage(ROOM_JID, "groupchat", "started", nicks),
+        )
         for nick in nicks:
-            self.assertEqual('init', self.plugin.games[ROOM_JID]['status'][nick])
-            self.assertEqual(self.plugin.player_init, self.plugin.games[ROOM_JID]['players_data'][nick])
-            self.plugin.games[ROOM_JID]['players_data'][nick]["xxx"] = nick
+            self.assertEqual("init", self.plugin.games[ROOM_JID]["status"][nick])
+            self.assertEqual(
+                self.plugin.player_init, self.plugin.games[ROOM_JID]["players_data"][nick]
+            )
+            self.plugin.games[ROOM_JID]["players_data"][nick]["xxx"] = nick
         for nick in nicks:
             # checks that a copy of self.player_init has been done and not a reference
-            self.assertEqual(nick, self.plugin.games[ROOM_JID]['players_data'][nick]['xxx'])
+            self.assertEqual(
+                nick, self.plugin.games[ROOM_JID]["players_data"][nick]["xxx"]
+            )
 
         # game exists, current profile is referee
         self.reinit(player_init={"xxx": "xyz"})
         self.initGame(0, 0)
-        self.plugin.games[ROOM_JID]['started'] = True
+        self.plugin.games[ROOM_JID]["started"] = True
         self.plugin.createGame(ROOM_JID, nicks, PROFILE)
-        self.assertEqual(self.host.getSentMessageXml(0), self._expectedMessage(ROOM_JID, "groupchat", "started", nicks))
+        self.assertEqual(
+            self.host.getSentMessageXml(0),
+            self._expectedMessage(ROOM_JID, "groupchat", "started", nicks),
+        )
 
         # game exists, current profile is not referee
         self.reinit(player_init={"xxx": "xyz"})
         self.initGame(0, 0)
-        self.plugin.games[ROOM_JID]['started'] = True
+        self.plugin.games[ROOM_JID]["started"] = True
         self.plugin_0045.joinRoom(0, 1)
         self.plugin.createGame(ROOM_JID, nicks, OTHER_PROFILE)
-        self.assertEqual(self.host.getSentMessage(0), None)  # no sync message has been sent by other_profile
+        self.assertEqual(
+            self.host.getSentMessage(0), None
+        )  # no sync message has been sent by other_profile
--- a/sat/test/test_plugin_misc_text_syntaxes.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/test/test_plugin_misc_text_syntaxes.py	Wed Jun 27 20:14:46 2018 +0200
@@ -91,10 +91,14 @@
     def test_html2text(self):
         """Check that html2text is not inserting \n in the middle of that link.
         By default lines are truncated after the 79th characters."""
-        source = "<img src=\"http://sat.goffi.org/static/images/screenshots/libervia/libervia_discussions.png\" alt=\"sat\"/>"
+        source = '<img src="http://sat.goffi.org/static/images/screenshots/libervia/libervia_discussions.png" alt="sat"/>'
         expected = "![sat](http://sat.goffi.org/static/images/screenshots/libervia/libervia_discussions.png)"
         try:
-            d = self.text_syntaxes.convert(source, self.text_syntaxes.SYNTAX_XHTML, self.text_syntaxes.SYNTAX_MARKDOWN)
+            d = self.text_syntaxes.convert(
+                source,
+                self.text_syntaxes.SYNTAX_XHTML,
+                self.text_syntaxes.SYNTAX_MARKDOWN,
+            )
         except plugin_misc_text_syntaxes.UnknownSyntax:
             raise SkipTest("Markdown syntax is not available.")
         d.addCallback(self.assertEqual, expected)
@@ -108,4 +112,3 @@
         expected = u"""test retest toto"""
         result = self.text_syntaxes._removeMarkups(self.EVIL_HTML2)
         self.assertEqual(re.sub(r"\s+", " ", result).rstrip(), expected.rstrip())
-
--- a/sat/test/test_plugin_xep_0033.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/test/test_plugin_xep_0033.py	Wed Jun 27 20:14:46 2018 +0200
@@ -36,11 +36,10 @@
 JID_STR_X_CC = Const.JID_STR[1]
 JID_STR_X_BCC = Const.JID_STR[2]
 
-ADDRS = ('to', JID_STR_X_TO, 'cc', JID_STR_X_CC, 'bcc', JID_STR_X_BCC)
+ADDRS = ("to", JID_STR_X_TO, "cc", JID_STR_X_CC, "bcc", JID_STR_X_BCC)
 
 
 class XEP_0033Test(helpers.SatTestCase):
-
     def setUp(self):
         self.host = helpers.FakeSAT()
         self.plugin = plugin.XEP_0033(self.host)
@@ -56,48 +55,67 @@
                 <address type='bcc' jid='%s'/>
             </addresses>
         </message>
-        """ % (JID_STR_FROM, JID_STR_TO, JID_STR_X_TO, JID_STR_X_CC, JID_STR_X_BCC)
+        """ % (
+            JID_STR_FROM,
+            JID_STR_TO,
+            JID_STR_X_TO,
+            JID_STR_X_CC,
+            JID_STR_X_BCC,
+        )
         stanza = parseXml(xml.encode("utf-8"))
         treatments = defer.Deferred()
-        self.plugin.messageReceivedTrigger(self.host.getClient(PROFILE), stanza, treatments)
-        data = {'extra': {}}
+        self.plugin.messageReceivedTrigger(
+            self.host.getClient(PROFILE), stanza, treatments
+        )
+        data = {"extra": {}}
 
         def cb(data):
-            expected = ('to', JID_STR_X_TO, 'cc', JID_STR_X_CC, 'bcc', JID_STR_X_BCC)
-            msg = 'Expected: %s\nGot:      %s' % (expected, data['extra']['addresses'])
-            self.assertEqual(data['extra']['addresses'], '%s:%s\n%s:%s\n%s:%s\n' % expected, msg)
+            expected = ("to", JID_STR_X_TO, "cc", JID_STR_X_CC, "bcc", JID_STR_X_BCC)
+            msg = "Expected: %s\nGot:      %s" % (expected, data["extra"]["addresses"])
+            self.assertEqual(
+                data["extra"]["addresses"], "%s:%s\n%s:%s\n%s:%s\n" % expected, msg
+            )
 
         treatments.addCallback(cb)
         return treatments.callback(data)
 
     def _get_mess_data(self):
-        mess_data = {"to": JID(JID_STR_TO),
-                     "type": "chat",
-                     "message": "content",
-                     "extra": {}
-                     }
-        mess_data["extra"]["address"] = '%s:%s\n%s:%s\n%s:%s\n' % ADDRS
+        mess_data = {
+            "to": JID(JID_STR_TO),
+            "type": "chat",
+            "message": "content",
+            "extra": {},
+        }
+        mess_data["extra"]["address"] = "%s:%s\n%s:%s\n%s:%s\n" % ADDRS
         original_stanza = u"""
         <message type="chat" from="%s" to="%s" id="test_1">
             <body>content</body>
         </message>
-        """ % (JID_STR_FROM, JID_STR_TO)
-        mess_data['xml'] = parseXml(original_stanza.encode("utf-8"))
+        """ % (
+            JID_STR_FROM,
+            JID_STR_TO,
+        )
+        mess_data["xml"] = parseXml(original_stanza.encode("utf-8"))
         return mess_data
 
     def _assertAddresses(self, mess_data):
         """The mess_data that we got here has been modified by self.plugin.messageSendTrigger,
         check that the addresses element has been added to the stanza."""
-        expected = self._get_mess_data()['xml']
-        addresses_extra = """
+        expected = self._get_mess_data()["xml"]
+        addresses_extra = (
+            """
         <addresses xmlns='http://jabber.org/protocol/address'>
             <address type='%s' jid='%s'/>
             <address type='%s' jid='%s'/>
             <address type='%s' jid='%s'/>
-        </addresses>""" % ADDRS
-        addresses_element = parseXml(addresses_extra.encode('utf-8'))
+        </addresses>"""
+            % ADDRS
+        )
+        addresses_element = parseXml(addresses_extra.encode("utf-8"))
         expected.addChild(addresses_element)
-        self.assertEqualXML(mess_data['xml'].toXml().encode("utf-8"), expected.toXml().encode("utf-8"))
+        self.assertEqualXML(
+            mess_data["xml"].toXml().encode("utf-8"), expected.toXml().encode("utf-8")
+        )
 
     def _checkSentAndStored(self):
         """Check that all the recipients got their messages and that the history has been filled.
@@ -108,7 +126,9 @@
 
         def cb(entities, to_jid):
             if host in entities:
-                if host not in sent:  # send the message to the entity offering the feature
+                if (
+                    host not in sent
+                ):  # send the message to the entity offering the feature
                     sent.append(host)
                     stored.append(host)
                 stored.append(to_jid)  # store in history for each recipient
@@ -127,7 +147,9 @@
 
         def cb_list(dummy):
             msg = "/!\ see the comments in XEP_0033.sendAndStoreMessage"
-            sent_recipients = [JID(elt['to']) for elt in self.host.getSentMessages(PROFILE_INDEX)]
+            sent_recipients = [
+                JID(elt["to"]) for elt in self.host.getSentMessages(PROFILE_INDEX)
+            ]
             self.assertEqualUnsortedList(sent_recipients, sent, msg)
             self.assertEqualUnsortedList(self.host.stored_messages, stored, msg)
 
@@ -143,10 +165,14 @@
         pre_treatments = defer.Deferred()
         post_treatments = defer.Deferred()
         helpers.muteLogging()
-        self.plugin.messageSendTrigger(self.host.getClient[PROFILE], data, pre_treatments, post_treatments)
+        self.plugin.messageSendTrigger(
+            self.host.getClient[PROFILE], data, pre_treatments, post_treatments
+        )
         post_treatments.callback(data)
         helpers.unmuteLogging()
-        post_treatments.addCallbacks(self._assertAddresses, lambda failure: failure.trap(CancelError))
+        post_treatments.addCallbacks(
+            self._assertAddresses, lambda failure: failure.trap(CancelError)
+        )
         return post_treatments
 
     def test_messageSendTriggerFeatureNotSupported(self):
--- a/sat/test/test_plugin_xep_0085.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/test/test_plugin_xep_0085.py	Wed Jun 27 20:14:46 2018 +0200
@@ -30,11 +30,16 @@
 
 
 class XEP_0085Test(helpers.SatTestCase):
-
     def setUp(self):
         self.host = helpers.FakeSAT()
         self.plugin = plugin.XEP_0085(self.host)
-        self.host.memory.setParam(plugin.PARAM_NAME, True, plugin.PARAM_KEY, C.NO_SECURITY_LIMIT, Const.PROFILE[0])
+        self.host.memory.setParam(
+            plugin.PARAM_NAME,
+            True,
+            plugin.PARAM_KEY,
+            C.NO_SECURITY_LIMIT,
+            Const.PROFILE[0],
+        )
 
     def test_messageReceived(self):
         for state in plugin.CHAT_STATES:
@@ -43,37 +48,51 @@
             %s
             <%s xmlns='%s'/>
             </message>
-            """ % (Const.JID_STR[1],
-                   Const.JID_STR[0],
-                   "<body>test</body>" if state == "active" else "",
-                   state, plugin.NS_CHAT_STATES)
+            """ % (
+                Const.JID_STR[1],
+                Const.JID_STR[0],
+                "<body>test</body>" if state == "active" else "",
+                state,
+                plugin.NS_CHAT_STATES,
+            )
             stanza = parseXml(xml.encode("utf-8"))
-            self.host.bridge.expectCall("chatStateReceived", Const.JID_STR[1], state, Const.PROFILE[0])
-            self.plugin.messageReceivedTrigger(self.host.getClient(Const.PROFILE[0]), stanza, None)
+            self.host.bridge.expectCall(
+                "chatStateReceived", Const.JID_STR[1], state, Const.PROFILE[0]
+            )
+            self.plugin.messageReceivedTrigger(
+                self.host.getClient(Const.PROFILE[0]), stanza, None
+            )
 
     def test_messageSendTrigger(self):
         def cb(data):
-            xml = data['xml'].toXml().encode("utf-8")
+            xml = data["xml"].toXml().encode("utf-8")
             self.assertEqualXML(xml, expected.toXml().encode("utf-8"))
 
         d_list = []
 
         for state in plugin.CHAT_STATES:
-            mess_data = {"to": Const.JID[0],
-                         "type": "chat",
-                         "message": "content",
-                         "extra": {} if state == "active" else {"chat_state": state}}
+            mess_data = {
+                "to": Const.JID[0],
+                "type": "chat",
+                "message": "content",
+                "extra": {} if state == "active" else {"chat_state": state},
+            }
             stanza = u"""
             <message type="chat" from="%s" to="%s" id="test_1">
             %s
             </message>
-            """ % (Const.JID_STR[1], Const.JID_STR[0],
-                   ("<body>%s</body>" % mess_data['message']) if state == "active" else "")
-            mess_data['xml'] = parseXml(stanza.encode("utf-8"))
-            expected = deepcopy(mess_data['xml'])
+            """ % (
+                Const.JID_STR[1],
+                Const.JID_STR[0],
+                ("<body>%s</body>" % mess_data["message"]) if state == "active" else "",
+            )
+            mess_data["xml"] = parseXml(stanza.encode("utf-8"))
+            expected = deepcopy(mess_data["xml"])
             expected.addElement(state, plugin.NS_CHAT_STATES)
             post_treatments = defer.Deferred()
-            self.plugin.messageSendTrigger(self.host.getClient(Const.PROFILE[0]), mess_data, None, post_treatments)
+            self.plugin.messageSendTrigger(
+                self.host.getClient(Const.PROFILE[0]), mess_data, None, post_treatments
+            )
 
             post_treatments.addCallback(cb)
             post_treatments.callback(mess_data)
--- a/sat/test/test_plugin_xep_0203.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/test/test_plugin_xep_0203.py	Wed Jun 27 20:14:46 2018 +0200
@@ -27,11 +27,10 @@
 from dateutil.tz import tzutc
 import datetime
 
-NS_PUBSUB = 'http://jabber.org/protocol/pubsub'
+NS_PUBSUB = "http://jabber.org/protocol/pubsub"
 
 
 class XEP_0203Test(helpers.SatTestCase):
-
     def setUp(self):
         self.host = helpers.FakeSAT()
         self.plugin = XEP_0203(self.host)
@@ -44,7 +43,8 @@
             Offline Storage
           </delay>
         """
-        message_xml = """
+        message_xml = (
+            """
         <message
             from='romeo@montague.net/orchard'
             to='juliet@capulet.com'
@@ -52,14 +52,16 @@
           <body>text</body>
           %s
         </message>
-        """ % delay_xml
+        """
+            % delay_xml
+        )
 
-        parent = domish.Element((None, 'message'))
-        parent['from'] = 'romeo@montague.net/orchard'
-        parent['to'] = 'juliet@capulet.com'
-        parent['type'] = 'chat'
-        parent.addElement('body', None, 'text')
+        parent = domish.Element((None, "message"))
+        parent["from"] = "romeo@montague.net/orchard"
+        parent["to"] = "juliet@capulet.com"
+        parent["type"] = "chat"
+        parent.addElement("body", None, "text")
         stamp = datetime.datetime(2002, 9, 10, 23, 8, 25, tzinfo=tzutc())
-        elt = self.plugin.delay(stamp, JID('capulet.com'), 'Offline Storage', parent)
+        elt = self.plugin.delay(stamp, JID("capulet.com"), "Offline Storage", parent)
         self.assertEqualXML(elt.toXml(), delay_xml, True)
         self.assertEqualXML(parent.toXml(), message_xml, True)
--- a/sat/test/test_plugin_xep_0277.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/test/test_plugin_xep_0277.py	Wed Jun 27 20:14:46 2018 +0200
@@ -29,7 +29,8 @@
 
 class XEP_0277Test(helpers.SatTestCase):
 
-    PUBSUB_ENTRY_1 = u"""
+    PUBSUB_ENTRY_1 = (
+        u"""
     <item id="c745a688-9b02-11e3-a1a3-c0143dd4fe51">
         <entry xmlns="%s">
             <title type="text">&lt;span&gt;titre&lt;/span&gt;</title>
@@ -43,9 +44,12 @@
         </author>
     </entry>
     </item>
-    """ % plugin_xep_0277.NS_ATOM
+    """
+        % plugin_xep_0277.NS_ATOM
+    )
 
-    PUBSUB_ENTRY_2 = u"""
+    PUBSUB_ENTRY_2 = (
+        u"""
     <item id="c745a688-9b02-11e3-a1a3-c0143dd4fe51">
         <entry xmlns='%s'>
             <title type="text">&lt;div&gt;titre&lt;/div&gt;</title>
@@ -61,7 +65,9 @@
         </author>
     </entry>
     </item>
-    """ % plugin_xep_0277.NS_ATOM
+    """
+        % plugin_xep_0277.NS_ATOM
+    )
 
     def setUp(self):
         self.host = helpers.FakeSAT()
@@ -72,39 +78,48 @@
 
             def addPEPEvent(self, *args):
                 pass
+
         self.host.plugins["XEP-0060"] = plugin_xep_0060.XEP_0060(self.host)
         self.host.plugins["XEP-0163"] = XEP_0163(self.host)
         reload(plugin_misc_text_syntaxes)  # reload the plugin to avoid conflict error
-        self.host.plugins["TEXT-SYNTAXES"] = plugin_misc_text_syntaxes.TextSyntaxes(self.host)
+        self.host.plugins["TEXT-SYNTAXES"] = plugin_misc_text_syntaxes.TextSyntaxes(
+            self.host
+        )
         self.plugin = plugin_xep_0277.XEP_0277(self.host)
 
     def test_item2mbdata_1(self):
-        expected = {u'id': u'c745a688-9b02-11e3-a1a3-c0143dd4fe51',
-                    u'atom_id': u'c745a688-9b02-11e3-a1a3-c0143dd4fe51',
-                    u'title': u'<span>titre</span>',
-                    u'updated': u'1392992199.0',
-                    u'published': u'1392992198.0',
-                    u'content': u'<p>contenu</p>texte sans balise<p>autre contenu</p>',
-                    u'content_xhtml': u'<div><p>contenu</p>texte sans balise<p>autre contenu</p></div>',
-                    u'author': u'test1@souliane.org'
-                    }
-        item_elt = ElementParser()(self.PUBSUB_ENTRY_1, namespace=NS_PUBSUB).elements().next()
+        expected = {
+            u"id": u"c745a688-9b02-11e3-a1a3-c0143dd4fe51",
+            u"atom_id": u"c745a688-9b02-11e3-a1a3-c0143dd4fe51",
+            u"title": u"<span>titre</span>",
+            u"updated": u"1392992199.0",
+            u"published": u"1392992198.0",
+            u"content": u"<p>contenu</p>texte sans balise<p>autre contenu</p>",
+            u"content_xhtml": u"<div><p>contenu</p>texte sans balise<p>autre contenu</p></div>",
+            u"author": u"test1@souliane.org",
+        }
+        item_elt = (
+            ElementParser()(self.PUBSUB_ENTRY_1, namespace=NS_PUBSUB).elements().next()
+        )
         d = self.plugin.item2mbdata(item_elt)
         d.addCallback(self.assertEqual, expected)
         return d
 
     def test_item2mbdata_2(self):
-        expected = {u'id': u'c745a688-9b02-11e3-a1a3-c0143dd4fe51',
-                    u'atom_id': u'c745a688-9b02-11e3-a1a3-c0143dd4fe51',
-                    u'title': u'<div>titre</div>',
-                    u'title_xhtml': u'<div><div style="">titre</div></div>',
-                    u'updated': u'1392992199.0',
-                    u'published': u'1392992198.0',
-                    u'content': u'<div><p>contenu</p>texte dans balise<p>autre contenu</p></div>',
-                    u'content_xhtml': u'<div><p>contenu</p>texte dans balise<p>autre contenu</p></div>',
-                    u'author': u'test1@souliane.org'
-                    }
-        item_elt = ElementParser()(self.PUBSUB_ENTRY_2, namespace=NS_PUBSUB).elements().next()
+        expected = {
+            u"id": u"c745a688-9b02-11e3-a1a3-c0143dd4fe51",
+            u"atom_id": u"c745a688-9b02-11e3-a1a3-c0143dd4fe51",
+            u"title": u"<div>titre</div>",
+            u"title_xhtml": u'<div><div style="">titre</div></div>',
+            u"updated": u"1392992199.0",
+            u"published": u"1392992198.0",
+            u"content": u"<div><p>contenu</p>texte dans balise<p>autre contenu</p></div>",
+            u"content_xhtml": u"<div><p>contenu</p>texte dans balise<p>autre contenu</p></div>",
+            u"author": u"test1@souliane.org",
+        }
+        item_elt = (
+            ElementParser()(self.PUBSUB_ENTRY_2, namespace=NS_PUBSUB).elements().next()
+        )
         d = self.plugin.item2mbdata(item_elt)
         d.addCallback(self.assertEqual, expected)
         return d
--- a/sat/test/test_plugin_xep_0297.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/test/test_plugin_xep_0297.py	Wed Jun 27 20:14:46 2018 +0200
@@ -30,18 +30,18 @@
 from wokkel.generic import parseXml
 
 
-NS_PUBSUB = 'http://jabber.org/protocol/pubsub'
+NS_PUBSUB = "http://jabber.org/protocol/pubsub"
 
 
 class XEP_0297Test(helpers.SatTestCase):
-
     def setUp(self):
         self.host = helpers.FakeSAT()
         self.plugin = XEP_0297(self.host)
-        self.host.plugins['XEP-0203'] = XEP_0203(self.host)
+        self.host.plugins["XEP-0203"] = XEP_0203(self.host)
 
     def test_delay(self):
-        stanza = parseXml("""
+        stanza = parseXml(
+            """
           <message from='juliet@capulet.lit/orchard'
                    id='0202197'
                    to='romeo@montague.lit'
@@ -51,7 +51,10 @@
                 <amorous/>
             </mood>
           </message>
-        """.encode('utf-8'))
+        """.encode(
+                "utf-8"
+            )
+        )
         output = """
           <message to='mercutio@verona.lit' type='chat'>
             <body>A most courteous exposition!</body>
@@ -71,8 +74,16 @@
           </message>
         """
         stamp = datetime.datetime(2010, 7, 10, 23, 8, 25, tzinfo=tzutc())
-        d = self.plugin.forward(stanza, JID('mercutio@verona.lit'), stamp,
-                                body='A most courteous exposition!',
-                                profile_key=C.PROFILE[0])
-        d.addCallback(lambda dummy: self.assertEqualXML(self.host.getSentMessageXml(0), output, True))
+        d = self.plugin.forward(
+            stanza,
+            JID("mercutio@verona.lit"),
+            stamp,
+            body="A most courteous exposition!",
+            profile_key=C.PROFILE[0],
+        )
+        d.addCallback(
+            lambda dummy: self.assertEqualXML(
+                self.host.getSentMessageXml(0), output, True
+            )
+        )
         return d
--- a/sat/test/test_plugin_xep_0313.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/test/test_plugin_xep_0313.py	Wed Jun 27 20:14:46 2018 +0200
@@ -33,13 +33,12 @@
 from sat_tmp.wokkel.rsm import RSMRequest
 from sat_tmp.wokkel.mam import buildForm, MAMRequest
 
-NS_PUBSUB = 'http://jabber.org/protocol/pubsub'
-SERVICE = 'sat-pubsub.tazar.int'
+NS_PUBSUB = "http://jabber.org/protocol/pubsub"
+SERVICE = "sat-pubsub.tazar.int"
 SERVICE_JID = JID(SERVICE)
 
 
 class XEP_0313Test(helpers.SatTestCase):
-
     def setUp(self):
         self.host = helpers.FakeSAT()
         self.plugin = XEP_0313(self.host)
@@ -52,9 +51,14 @@
         <iq type='set' id='%s' to='%s'>
           <query xmlns='urn:xmpp:mam:1'/>
         </iq>
-        """ % (("H_%d" % domish.Element._idCounter), SERVICE)
+        """ % (
+            ("H_%d" % domish.Element._idCounter),
+            SERVICE,
+        )
         d = self.plugin.queryArchive(self.client, MAMRequest(), SERVICE_JID)
-        d.addCallback(lambda dummy: self.assertEqualXML(self.host.getSentMessageXml(0), xml, True))
+        d.addCallback(
+            lambda dummy: self.assertEqualXML(self.host.getSentMessageXml(0), xml, True)
+        )
         return d
 
     def test_queryArchivePubsub(self):
@@ -62,9 +66,16 @@
         <iq type='set' id='%s' to='%s'>
           <query xmlns='urn:xmpp:mam:1' node='fdp/submitted/capulet.lit/sonnets' />
         </iq>
-        """ % (("H_%d" % domish.Element._idCounter), SERVICE)
-        d = self.plugin.queryArchive(self.client, MAMRequest(node="fdp/submitted/capulet.lit/sonnets"), SERVICE_JID)
-        d.addCallback(lambda dummy: self.assertEqualXML(self.host.getSentMessageXml(0), xml, True))
+        """ % (
+            ("H_%d" % domish.Element._idCounter),
+            SERVICE,
+        )
+        d = self.plugin.queryArchive(
+            self.client, MAMRequest(node="fdp/submitted/capulet.lit/sonnets"), SERVICE_JID
+        )
+        d.addCallback(
+            lambda dummy: self.assertEqualXML(self.host.getSentMessageXml(0), xml, True)
+        )
         return d
 
     def test_queryArchiveWith(self):
@@ -81,10 +92,15 @@
             </x>
           </query>
         </iq>
-        """ % (("H_%d" % domish.Element._idCounter), SERVICE)
-        form = buildForm(with_jid=JID('juliet@capulet.lit'))
+        """ % (
+            ("H_%d" % domish.Element._idCounter),
+            SERVICE,
+        )
+        form = buildForm(with_jid=JID("juliet@capulet.lit"))
         d = self.plugin.queryArchive(self.client, MAMRequest(form), SERVICE_JID)
-        d.addCallback(lambda dummy: self.assertEqualXML(self.host.getSentMessageXml(0), xml, True))
+        d.addCallback(
+            lambda dummy: self.assertEqualXML(self.host.getSentMessageXml(0), xml, True)
+        )
         return d
 
     def test_queryArchiveStartEnd(self):
@@ -104,12 +120,17 @@
             </x>
           </query>
         </iq>
-        """ % (("H_%d" % domish.Element._idCounter), SERVICE)
+        """ % (
+            ("H_%d" % domish.Element._idCounter),
+            SERVICE,
+        )
         start = datetime.datetime(2010, 6, 7, 0, 0, 0, tzinfo=tzutc())
         end = datetime.datetime(2010, 7, 7, 13, 23, 54, tzinfo=tzutc())
         form = buildForm(start=start, end=end)
         d = self.plugin.queryArchive(self.client, MAMRequest(form), SERVICE_JID)
-        d.addCallback(lambda dummy: self.assertEqualXML(self.host.getSentMessageXml(0), xml, True))
+        d.addCallback(
+            lambda dummy: self.assertEqualXML(self.host.getSentMessageXml(0), xml, True)
+        )
         return d
 
     def test_queryArchiveStart(self):
@@ -126,11 +147,16 @@
             </x>
           </query>
         </iq>
-        """ % (("H_%d" % domish.Element._idCounter), SERVICE)
+        """ % (
+            ("H_%d" % domish.Element._idCounter),
+            SERVICE,
+        )
         start = datetime.datetime(2010, 8, 7, 0, 0, 0, tzinfo=tzutc())
         form = buildForm(start=start)
         d = self.plugin.queryArchive(self.client, MAMRequest(form), SERVICE_JID)
-        d.addCallback(lambda dummy: self.assertEqualXML(self.host.getSentMessageXml(0), xml, True))
+        d.addCallback(
+            lambda dummy: self.assertEqualXML(self.host.getSentMessageXml(0), xml, True)
+        )
         return d
 
     def test_queryArchiveRSM(self):
@@ -150,12 +176,17 @@
             </set>
           </query>
         </iq>
-        """ % (("H_%d" % domish.Element._idCounter), SERVICE)
+        """ % (
+            ("H_%d" % domish.Element._idCounter),
+            SERVICE,
+        )
         start = datetime.datetime(2010, 8, 7, 0, 0, 0, tzinfo=tzutc())
         form = buildForm(start=start)
         rsm = RSMRequest(max_=10)
         d = self.plugin.queryArchive(self.client, MAMRequest(form, rsm), SERVICE_JID)
-        d.addCallback(lambda dummy: self.assertEqualXML(self.host.getSentMessageXml(0), xml, True))
+        d.addCallback(
+            lambda dummy: self.assertEqualXML(self.host.getSentMessageXml(0), xml, True)
+        )
         return d
 
     def test_queryArchiveRSMPaging(self):
@@ -172,12 +203,17 @@
               </set>
           </query>
         </iq>
-        """ % (("H_%d" % domish.Element._idCounter), SERVICE)
+        """ % (
+            ("H_%d" % domish.Element._idCounter),
+            SERVICE,
+        )
         start = datetime.datetime(2010, 8, 7, 0, 0, 0, tzinfo=tzutc())
         form = buildForm(start=start)
-        rsm = RSMRequest(max_=10, after=u'09af3-cc343-b409f')
+        rsm = RSMRequest(max_=10, after=u"09af3-cc343-b409f")
         d = self.plugin.queryArchive(self.client, MAMRequest(form, rsm), SERVICE_JID)
-        d.addCallback(lambda dummy: self.assertEqualXML(self.host.getSentMessageXml(0), xml, True))
+        d.addCallback(
+            lambda dummy: self.assertEqualXML(self.host.getSentMessageXml(0), xml, True)
+        )
         return d
 
     def test_queryFields(self):
@@ -185,9 +221,14 @@
         <iq type='get' id="%s" to='%s'>
           <query xmlns='urn:xmpp:mam:1'/>
         </iq>
-        """ % (("H_%d" % domish.Element._idCounter), SERVICE)
+        """ % (
+            ("H_%d" % domish.Element._idCounter),
+            SERVICE,
+        )
         d = self.plugin.queryFields(self.client, SERVICE_JID)
-        d.addCallback(lambda dummy: self.assertEqualXML(self.host.getSentMessageXml(0), xml, True))
+        d.addCallback(
+            lambda dummy: self.assertEqualXML(self.host.getSentMessageXml(0), xml, True)
+        )
         return d
 
     def test_queryArchiveFields(self):
@@ -207,13 +248,27 @@
             </x>
           </query>
         </iq>
-        """ % (("H_%d" % domish.Element._idCounter), SERVICE)
-        extra_fields = [Field('text-single', 'urn:example:xmpp:free-text-search', 'Where arth thou, my Juliet?'),
-                       Field('text-single', 'urn:example:xmpp:stanza-content', '{http://jabber.org/protocol/mood}mood/lonely')
-                       ]
+        """ % (
+            ("H_%d" % domish.Element._idCounter),
+            SERVICE,
+        )
+        extra_fields = [
+            Field(
+                "text-single",
+                "urn:example:xmpp:free-text-search",
+                "Where arth thou, my Juliet?",
+            ),
+            Field(
+                "text-single",
+                "urn:example:xmpp:stanza-content",
+                "{http://jabber.org/protocol/mood}mood/lonely",
+            ),
+        ]
         form = buildForm(extra_fields=extra_fields)
         d = self.plugin.queryArchive(self.client, MAMRequest(form), SERVICE_JID)
-        d.addCallback(lambda dummy: self.assertEqualXML(self.host.getSentMessageXml(0), xml, True))
+        d.addCallback(
+            lambda dummy: self.assertEqualXML(self.host.getSentMessageXml(0), xml, True)
+        )
         return d
 
     def test_queryPrefs(self):
@@ -224,9 +279,14 @@
             <never/>
           </prefs>
         </iq>
-        """ % (("H_%d" % domish.Element._idCounter), SERVICE)
+        """ % (
+            ("H_%d" % domish.Element._idCounter),
+            SERVICE,
+        )
         d = self.plugin.getPrefs(self.client, SERVICE_JID)
-        d.addCallback(lambda dummy: self.assertEqualXML(self.host.getSentMessageXml(0), xml, True))
+        d.addCallback(
+            lambda dummy: self.assertEqualXML(self.host.getSentMessageXml(0), xml, True)
+        )
         return d
 
     def test_setPrefs(self):
@@ -241,9 +301,14 @@
             </never>
           </prefs>
         </iq>
-        """ % (("H_%d" % domish.Element._idCounter), SERVICE)
-        always = [JID('romeo@montague.lit')]
-        never = [JID('montague@montague.lit')]
+        """ % (
+            ("H_%d" % domish.Element._idCounter),
+            SERVICE,
+        )
+        always = [JID("romeo@montague.lit")]
+        never = [JID("montague@montague.lit")]
         d = self.plugin.setPrefs(self.client, SERVICE_JID, always=always, never=never)
-        d.addCallback(lambda dummy: self.assertEqualXML(self.host.getSentMessageXml(0), xml, True))
+        d.addCallback(
+            lambda dummy: self.assertEqualXML(self.host.getSentMessageXml(0), xml, True)
+        )
         return d
--- a/sat/test/test_plugin_xep_0334.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/test/test_plugin_xep_0334.py	Wed Jun 27 20:14:46 2018 +0200
@@ -27,11 +27,10 @@
 from wokkel.generic import parseXml
 from sat.core import exceptions
 
-HINTS = ('no-permanent-storage', 'no-storage', 'no-copy')
+HINTS = ("no-permanent-storage", "no-storage", "no-copy")
 
 
 class XEP_0334Test(helpers.SatTestCase):
-
     def setUp(self):
         self.host = helpers.FakeSAT()
         self.plugin = XEP_0334(self.host)
@@ -46,20 +45,23 @@
           %s
         </message>
         """
-        original_xml = template_xml % ''
+        original_xml = template_xml % ""
 
         d_list = []
 
         def cb(data, expected_xml):
-            result_xml = data['xml'].toXml().encode("utf-8")
+            result_xml = data["xml"].toXml().encode("utf-8")
             self.assertEqualXML(result_xml, expected_xml, True)
 
-        for key in (HINTS + ('', 'dummy_hint')):
-            mess_data = {'xml': parseXml(original_xml.encode("utf-8")),
-                         'extra': {key: True}
-                         }
+        for key in HINTS + ("", "dummy_hint"):
+            mess_data = {
+                "xml": parseXml(original_xml.encode("utf-8")),
+                "extra": {key: True},
+            }
             treatments = defer.Deferred()
-            self.plugin.messageSendTrigger(self.host.getClient(C.PROFILE[0]), mess_data, defer.Deferred(), treatments)
+            self.plugin.messageSendTrigger(
+                self.host.getClient(C.PROFILE[0]), mess_data, defer.Deferred(), treatments
+            )
             if treatments.callbacks:  # the trigger added a callback
                 expected_xml = template_xml % ('<%s xmlns="urn:xmpp:hints"/>' % key)
                 treatments.addCallback(cb, expected_xml)
@@ -87,16 +89,18 @@
 
         d_list = []
 
-        for key in (HINTS + ('dummy_hint',)):
+        for key in HINTS + ("dummy_hint",):
             message = parseXml(template_xml % ('<%s xmlns="urn:xmpp:hints"/>' % key))
             post_treat = defer.Deferred()
-            self.plugin.messageReceivedTrigger(self.host.getClient(C.PROFILE[0]), message, post_treat)
+            self.plugin.messageReceivedTrigger(
+                self.host.getClient(C.PROFILE[0]), message, post_treat
+            )
             if post_treat.callbacks:
-                assert(key in ('no-permanent-storage', 'no-storage'))
+                assert key in ("no-permanent-storage", "no-storage")
                 post_treat.addCallbacks(cb, eb)
                 post_treat.callback(None)
                 d_list.append(post_treat)
             else:
-                assert(key not in ('no-permanent-storage', 'no-storage'))
+                assert key not in ("no-permanent-storage", "no-storage")
 
         return defer.DeferredList(d_list)
--- a/sat/tools/common/ansi.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/tools/common/ansi.py	Wed Jun 27 20:14:46 2018 +0200
@@ -19,15 +19,18 @@
 
 import sys
 
+
 class ANSI(object):
 
     ## ANSI escape sequences ##
-    RESET = '\033[0m'
-    NORMAL_WEIGHT = '\033[22m'
-    FG_BLACK, FG_RED, FG_GREEN, FG_YELLOW, FG_BLUE, FG_MAGENTA, FG_CYAN, FG_WHITE = ('\033[3%dm' % nb for nb in xrange(8))
-    BOLD = '\033[1m'
-    BLINK = '\033[5m'
-    BLINK_OFF = '\033[25m'
+    RESET = "\033[0m"
+    NORMAL_WEIGHT = "\033[22m"
+    FG_BLACK, FG_RED, FG_GREEN, FG_YELLOW, FG_BLUE, FG_MAGENTA, FG_CYAN, FG_WHITE = (
+        "\033[3%dm" % nb for nb in xrange(8)
+    )
+    BOLD = "\033[1m"
+    BLINK = "\033[5m"
+    BLINK_OFF = "\033[25m"
 
     @classmethod
     def color(cls, *args):
@@ -39,16 +42,19 @@
         if args[-1] != cls.RESET:
             args = list(args)
             args.append(cls.RESET)
-        return u''.join(args)
+        return u"".join(args)
 
 
 try:
     tty = sys.stdout.isatty()
-except (AttributeError, TypeError):  # FIXME: TypeError is here for Pyjamas, need to be removed
+except (
+    AttributeError,
+    TypeError,
+):  # FIXME: TypeError is here for Pyjamas, need to be removed
     tty = False
 if not tty:
-    # we don't want ANSI escape codes if we are not outputing to a tty!
+    #  we don't want ANSI escape codes if we are not outputing to a tty!
     for attr in dir(ANSI):
         if isinstance(getattr(ANSI, attr), basestring):
-            setattr(ANSI, attr, u'')
+            setattr(ANSI, attr, u"")
 del tty
--- a/sat/tools/common/data_format.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/tools/common/data_format.py	Wed Jun 27 20:14:46 2018 +0200
@@ -18,10 +18,11 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 """ tools common to backend and frontends """
-# FIXME: json may be more appropriate than manual serialising like done here
+#  FIXME: json may be more appropriate than manual serialising like done here
 
 from sat.core import exceptions
 
+
 def dict2iter(name, dict_, pop=False):
     """iterate into a list serialised in a dict
 
@@ -36,24 +37,25 @@
     @return iter: iterate through the deserialised list
     """
     if pop:
-        get=lambda d,k: d.pop(k)
+        get = lambda d, k: d.pop(k)
     else:
-        get=lambda d,k: d[k]
+        get = lambda d, k: d[k]
 
     try:
-        yield get(dict_,name)
+        yield get(dict_, name)
     except KeyError:
         return
     else:
         idx = 1
         while True:
             try:
-                yield get(dict_,u'{}#{}'.format(name, idx))
+                yield get(dict_, u"{}#{}".format(name, idx))
             except KeyError:
                 return
             else:
                 idx += 1
 
+
 def dict2iterdict(name, dict_, extra_keys, pop=False):
     """like dict2iter but yield dictionaries
 
@@ -61,17 +63,20 @@
     e.g. dict2iterdict(comments, mb_data, ('node', 'service')) will yield dicts like:
         {u'comments': u'value1', u'node': u'value2', u'service': u'value3'}
     """
-    # FIXME: this format seem overcomplicated, it may be more appropriate to use json here
+    #  FIXME: this format seem overcomplicated, it may be more appropriate to use json here
     if pop:
-        get=lambda d,k: d.pop(k)
+        get = lambda d, k: d.pop(k)
     else:
-        get=lambda d,k: d[k]
+        get = lambda d, k: d[k]
     for idx, main_value in enumerate(dict2iter(name, dict_, pop=pop)):
         ret = {name: main_value}
         for k in extra_keys:
-            ret[k] = get(dict_, u'{}{}_{}'.format(name, (u'#' + unicode(idx)) if idx else u'', k))
+            ret[k] = get(
+                dict_, u"{}{}_{}".format(name, (u"#" + unicode(idx)) if idx else u"", k)
+            )
         yield ret
 
+
 def iter2dict(name, iter_, dict_=None, check_conflict=True):
     """Fill a dict with values from an iterable
 
@@ -92,13 +97,14 @@
         if idx == 0:
             key = name
         else:
-            key = u'{}#{}'.format(name, idx)
+            key = u"{}#{}".format(name, idx)
         if check_conflict and key in dict_:
             raise exceptions.ConflictError
         dict_[key] = value
     return dict
 
-def getSubDict(name, dict_, sep=u'_'):
+
+def getSubDict(name, dict_, sep=u"_"):
     """get a sub dictionary from a serialised dictionary
 
     look for keys starting with name, and create a dict with it
@@ -109,7 +115,7 @@
     @param sep(unicode): separator used between name and subkey
     @return iter: iterate through the deserialised items
     """
-    for k,v in dict_.iteritems():
+    for k, v in dict_.iteritems():
         if k.startswith(name):
             if k == name:
                 yield None, v
@@ -117,4 +123,4 @@
                 if k[len(name)] != sep:
                     continue
                 else:
-                    yield k[len(name)+1:], v
+                    yield k[len(name) + 1 :], v
--- a/sat/tools/common/data_objects.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/tools/common/data_objects.py	Wed Jun 27 20:14:46 2018 +0200
@@ -20,6 +20,7 @@
 """Objects handling bridge data, with jinja2 safe markup handling"""
 
 from sat.tools.common import data_format
+
 try:
     from jinja2 import Markup as safe
 except ImportError:
@@ -28,11 +29,10 @@
 from sat.tools.common import uri as xmpp_uri
 import urllib
 
-q = lambda value: urllib.quote(value.encode('utf-8'), safe='@')
+q = lambda value: urllib.quote(value.encode("utf-8"), safe="@")
 
 
 class BlogItem(object):
-
     def __init__(self, mb_data, parent):
         self.mb_data = mb_data
         self.parent = parent
@@ -43,97 +43,97 @@
 
     @property
     def id(self):
-        return self.mb_data.get(u'id')
+        return self.mb_data.get(u"id")
 
     @property
     def atom_id(self):
-        return self.mb_data.get(u'atom_id')
+        return self.mb_data.get(u"atom_id")
 
     @property
     def uri(self):
         node = self.parent.node
         service = self.parent.service
-        return xmpp_uri.buildXMPPUri(u'pubsub',
-                                     subtype=u'microblog',
-                                     path=service,
-                                     node=node,
-                                     item=self.id)
+        return xmpp_uri.buildXMPPUri(
+            u"pubsub", subtype=u"microblog", path=service, node=node, item=self.id
+        )
 
     @property
     def published(self):
-        return self.mb_data.get(u'published')
+        return self.mb_data.get(u"published")
 
     @property
     def updated(self):
-        return self.mb_data.get(u'updated')
+        return self.mb_data.get(u"updated")
 
     @property
     def language(self):
-        return self.mb_data.get(u'language')
+        return self.mb_data.get(u"language")
 
     @property
     def author(self):
-        return self.mb_data.get(u'author')
+        return self.mb_data.get(u"author")
 
     @property
     def author_jid(self):
-        return self.mb_data.get(u'author_jid')
+        return self.mb_data.get(u"author_jid")
 
     @property
     def author_jid_verified(self):
-        return self.mb_data.get(u'author_jid_verified')
+        return self.mb_data.get(u"author_jid_verified")
 
     @property
     def author_email(self):
-        return self.mb_data.get(u'author_email')
+        return self.mb_data.get(u"author_email")
 
     @property
     def tags(self):
         if self._tags is None:
-            self._tags = list(data_format.dict2iter('tag', self.mb_data))
+            self._tags = list(data_format.dict2iter("tag", self.mb_data))
         return self._tags
 
     @property
     def groups(self):
         if self._groups is None:
-            self._groups = list(data_format.dict2iter('group', self.mb_data))
+            self._groups = list(data_format.dict2iter("group", self.mb_data))
         return self._groups
 
     @property
     def title(self):
-        return self.mb_data.get(u'title')
+        return self.mb_data.get(u"title")
 
     @property
     def title_xhtml(self):
         try:
-            return safe(self.mb_data[u'title_xhtml'])
+            return safe(self.mb_data[u"title_xhtml"])
         except KeyError:
             return None
 
     @property
     def content(self):
-        return self.mb_data.get(u'content')
+        return self.mb_data.get(u"content")
 
     @property
     def content_xhtml(self):
         try:
-            return safe(self.mb_data[u'content_xhtml'])
+            return safe(self.mb_data[u"content_xhtml"])
         except KeyError:
             return None
 
     @property
     def comments(self):
         if self._comments is None:
-            self._comments = data_format.dict2iterdict(u'comments', self.mb_data, (u'node', u'service'))
+            self._comments = data_format.dict2iterdict(
+                u"comments", self.mb_data, (u"node", u"service")
+            )
         return self._comments
 
     @property
     def comments_service(self):
-        return self.mb_data.get(u'comments_service')
+        return self.mb_data.get(u"comments_service")
 
     @property
     def comments_node(self):
-        return self.mb_data.get(u'comments_node')
+        return self.mb_data.get(u"comments_node")
 
     @property
     def comments_items_list(self):
@@ -147,22 +147,21 @@
 
 
 class BlogItems(object):
-
     def __init__(self, mb_data):
         self.items = [BlogItem(i, self) for i in mb_data[0]]
         self.metadata = mb_data[1]
 
     @property
     def service(self):
-        return self.metadata[u'service']
+        return self.metadata[u"service"]
 
     @property
     def node(self):
-        return self.metadata[u'node']
+        return self.metadata[u"node"]
 
     @property
     def uri(self):
-        return self.metadata[u'uri']
+        return self.metadata[u"uri"]
 
     def __len__(self):
         return self.items.__len__()
@@ -184,7 +183,6 @@
 
 
 class Message(object):
-
     def __init__(self, msg_data):
         self._uid = msg_data[0]
         self._timestamp = msg_data[1]
@@ -194,7 +192,7 @@
         self._subject_data = msg_data[5]
         self._type = msg_data[6]
         self._extra = msg_data[7]
-        self._html = dict(data_format.getSubDict('xhtml', self._extra))
+        self._html = dict(data_format.getSubDict("xhtml", self._extra))
 
     @property
     def id(self):
@@ -211,14 +209,14 @@
     @property
     def text(self):
         try:
-            return self._message_data['']
+            return self._message_data[""]
         except KeyError:
             return next(self._message_data.itervalues())
 
     @property
     def subject(self):
         try:
-            return self._subject_data['']
+            return self._subject_data[""]
         except KeyError:
             return next(self._subject_data.itervalues())
 
@@ -228,36 +226,35 @@
 
     @property
     def thread(self):
-        return self._extra.get('thread')
+        return self._extra.get("thread")
 
     @property
     def thread_parent(self):
-        return self._extra.get('thread_parent')
+        return self._extra.get("thread_parent")
 
     @property
     def received(self):
-        return self._extra.get('received_timestamp', self._timestamp)
+        return self._extra.get("received_timestamp", self._timestamp)
 
     @property
     def delay_sender(self):
-        return self._extra.get('delay_sender')
+        return self._extra.get("delay_sender")
 
     @property
     def info_type(self):
-        return self._extra.get('info_type')
+        return self._extra.get("info_type")
 
     @property
     def html(self):
         if not self._html:
             return None
         try:
-            return safe(self._html[''])
+            return safe(self._html[""])
         except KeyError:
             return safe(next(self._html.itervalues()))
 
 
 class Messages(object):
-
     def __init__(self, msgs_data):
         self.messages = [Message(m) for m in msgs_data]
 
@@ -279,8 +276,8 @@
     def __contains__(self, item):
         return self.messages.__contains__(item)
 
+
 class Room(object):
-
     def __init__(self, jid, name=None, url=None):
         self.jid = jid
         self.name = name or jid
@@ -289,7 +286,6 @@
 
 
 class Identity(object):
-
     def __init__(self, jid_str, data=None):
         self.jid_str = jid_str
         self.data = data if data is not None else {}
@@ -305,7 +301,6 @@
 
 
 class Identities(object):
-
     def __init__(self):
         self.identities = {}
 
@@ -352,5 +347,6 @@
 
         values will be quoted before being used
         """
-        return self.url.format(*[q(a) for a in args],
-            **{k: ObjectQuoter(v) for k,v in kwargs.iteritems()})
+        return self.url.format(
+            *[q(a) for a in args], **{k: ObjectQuoter(v) for k, v in kwargs.iteritems()}
+        )
--- a/sat/tools/common/date_utils.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/tools/common/date_utils.py	Wed Jun 27 20:14:46 2018 +0200
@@ -36,7 +36,15 @@
     return calendar.timegm(dateutil_parser.parse(unicode(value)).utctimetuple())
 
 
-def date_fmt(timestamp, fmt='short', date_only=False, auto_limit=7, auto_old_fmt='short', auto_new_fmt='relative', locale_str=C.DEFAULT_LOCALE):
+def date_fmt(
+    timestamp,
+    fmt="short",
+    date_only=False,
+    auto_limit=7,
+    auto_old_fmt="short",
+    auto_new_fmt="relative",
+    locale_str=C.DEFAULT_LOCALE,
+):
     """format date according to locale
 
     @param timestamp(basestring, int): unix time
@@ -63,9 +71,9 @@
         than limit
 
     """
-    if fmt == 'auto_day':
-        fmt, auto_limit, auto_old_fmt, auto_new_fmt = 'auto', 0, 'short', 'HH:mm'
-    if fmt == 'auto':
+    if fmt == "auto_day":
+        fmt, auto_limit, auto_old_fmt, auto_new_fmt = "auto", 0, "short", "HH:mm"
+    if fmt == "auto":
         if auto_limit == 0:
             today = time.mktime(datetime.date.today().timetuple())
             if int(timestamp) < today:
@@ -79,15 +87,17 @@
             else:
                 fmt = auto_new_fmt
 
-    if fmt == 'relative':
+    if fmt == "relative":
         delta = int(timestamp) - time.time()
-        return dates.format_timedelta(delta, granularity="minute", add_direction=True, locale=locale_str)
-    elif fmt in ('short', 'long'):
+        return dates.format_timedelta(
+            delta, granularity="minute", add_direction=True, locale=locale_str
+        )
+    elif fmt in ("short", "long"):
         formatter = dates.format_date if date_only else dates.format_datetime
         return formatter(int(timestamp), format=fmt, locale=locale_str)
-    elif fmt == 'iso':
+    elif fmt == "iso":
         if date_only:
-            fmt = 'yyyy-MM-dd'
+            fmt = "yyyy-MM-dd"
         else:
             fmt = "yyyy-MM-ddTHH:mm:ss'Z'"
         return dates.format_datetime(int(timestamp), format=fmt)
--- a/sat/tools/common/dynamic_import.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/tools/common/dynamic_import.py	Wed Jun 27 20:14:46 2018 +0200
@@ -22,7 +22,7 @@
 from importlib import import_module
 
 
-def bridge(name, module_path='sat.bridge'):
+def bridge(name, module_path="sat.bridge"):
     """Import bridge module
 
     @param module_path(str): path of the module to import
@@ -30,10 +30,10 @@
     @return (module, None): imported module or None if nothing is found
     """
     try:
-        bridge_module = import_module(module_path + '.' + name)
+        bridge_module = import_module(module_path + "." + name)
     except ImportError:
         try:
-            bridge_module = import_module(module_path + '.' + name + '_bridge')
+            bridge_module = import_module(module_path + "." + name + "_bridge")
         except ImportError:
             bridge_module = None
     return bridge_module
--- a/sat/tools/common/files_utils.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/tools/common/files_utils.py	Wed Jun 27 20:14:46 2018 +0200
@@ -30,6 +30,6 @@
     ori_path = path
     idx = 1
     while os.path.exists(path):
-        path = ori_path + u'_' + unicode(idx)
+        path = ori_path + u"_" + unicode(idx)
         idx += 1
     return path
--- a/sat/tools/common/regex.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/tools/common/regex.py	Wed Jun 27 20:14:46 2018 +0200
@@ -20,16 +20,17 @@
 """ regex tools common to backend and frontends """
 
 import re
-path_escape = {'%': '%25', '/': '%2F', '\\': '%5c'}
-path_escape_rev = {re.escape(v):k for k, v in path_escape.iteritems()}
-path_escape = {re.escape(k):v for k, v in path_escape.iteritems()}
-# thanks to Martijn Pieters (https://stackoverflow.com/a/14693789)
-RE_ANSI_REMOVE = re.compile(r'\x1b[^m]*m')
+
+path_escape = {"%": "%25", "/": "%2F", "\\": "%5c"}
+path_escape_rev = {re.escape(v): k for k, v in path_escape.iteritems()}
+path_escape = {re.escape(k): v for k, v in path_escape.iteritems()}
+#  thanks to Martijn Pieters (https://stackoverflow.com/a/14693789)
+RE_ANSI_REMOVE = re.compile(r"\x1b[^m]*m")
 
 
 def reJoin(exps):
     """Join (OR) various regexes"""
-    return re.compile('|'.join(exps))
+    return re.compile("|".join(exps))
 
 
 def reSubDict(pattern, repl_dict, string):
@@ -42,6 +43,7 @@
     """
     return pattern.sub(lambda m: repl_dict[re.escape(m.group(0))], string)
 
+
 path_escape_re = reJoin(path_escape.keys())
 path_escape_rev_re = reJoin(path_escape_rev.keys())
 
@@ -63,10 +65,11 @@
     """
     return reSubDict(path_escape_rev_re, path_escape_rev, string)
 
+
 def ansiRemove(string):
     """Remove ANSI escape codes from string
 
     @param string(basestr): string to filter
     @return (str, unicode): string without ANSI escape codes
     """
-    return RE_ANSI_REMOVE.sub('', string)
+    return RE_ANSI_REMOVE.sub("", string)
--- a/sat/tools/common/template.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/tools/common/template.py	Wed Jun 27 20:14:46 2018 +0200
@@ -24,6 +24,7 @@
 from sat.core import exceptions
 from sat.tools.common import date_utils
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 import os.path
 from xml.sax.saxutils import quoteattr
@@ -35,30 +36,34 @@
 import pygments
 from pygments import lexers
 from pygments import formatters
+
 try:
     import sat_templates
 except ImportError:
-    raise exceptions.MissingModule(u'sat_templates module is not available, please install it or check your path to use template engine')
+    raise exceptions.MissingModule(
+        u"sat_templates module is not available, please install it or check your path to use template engine"
+    )
 else:
     sat_templates  # to avoid pyflakes warning
 
 try:
     import jinja2
 except:
-    raise exceptions.MissingModule(u'Missing module jinja2, please install it from http://jinja.pocoo.org or with pip install jinja2')
+    raise exceptions.MissingModule(
+        u"Missing module jinja2, please install it from http://jinja.pocoo.org or with pip install jinja2"
+    )
 
 from jinja2 import Markup as safe
 from jinja2 import is_undefined
 from lxml import etree
 
-HTML_EXT = ('html', 'xhtml')
-RE_ATTR_ESCAPE = re.compile(r'[^a-z_-]')
-# TODO: handle external path (an additional search path for templates should be settable by user
+HTML_EXT = ("html", "xhtml")
+RE_ATTR_ESCAPE = re.compile(r"[^a-z_-]")
+#  TODO: handle external path (an additional search path for templates should be settable by user
 # TODO: handle absolute URL (should be used for trusted use cases) only (e.g. jp) for security reason
 
 
 class TemplateLoader(jinja2.FileSystemLoader):
-
     def __init__(self):
         searchpath = os.path.dirname(sat_templates.__file__)
         super(TemplateLoader, self).__init__(searchpath, followlinks=True)
@@ -72,20 +77,20 @@
             relative path is the path from search path with theme specified
             e.g. default/blog/articles.html
         """
-        if template.startswith(u'('):
+        if template.startswith(u"("):
             try:
-                theme_end = template.index(u')')
+                theme_end = template.index(u")")
             except IndexError:
                 raise ValueError(u"incorrect theme in template")
             theme = template[1:theme_end]
-            template = template[theme_end+1:]
-            if not template or template.startswith(u'/'):
+            template = template[theme_end + 1 :]
+            if not template or template.startswith(u"/"):
                 raise ValueError(u"incorrect path after template name")
             template = os.path.join(theme, template)
-        elif template.startswith(u'/'):
+        elif template.startswith(u"/"):
             # absolute path means no template
             theme = None
-            raise NotImplementedError(u'absolute path is not implemented yet')
+            raise NotImplementedError(u"absolute path is not implemented yet")
         else:
             theme = C.TEMPLATE_THEME_DEFAULT
             template = os.path.join(theme, template)
@@ -99,11 +104,11 @@
         @return (unicode, None): default path or None if there is not
         """
         ext = os.path.splitext(template_path)[1][1:]
-        path_elems = template_path.split(u'/')
+        path_elems = template_path.split(u"/")
         if ext in HTML_EXT:
-            if path_elems[1] == u'error':
+            if path_elems[1] == u"error":
                 # if an inexisting error page is requested, we return base page
-                default_path = os.path.join(theme, u'error/base.html')
+                default_path = os.path.join(theme, u"error/base.html")
                 return default_path
         if theme != C.TEMPLATE_THEME_DEFAULT:
             # if template doesn't exists for this theme, we try with default
@@ -124,7 +129,9 @@
             if theme is not None:
                 default_path = self.get_default_template(theme, template_path)
                 if default_path is not None:
-                    return super(TemplateLoader, self).get_source(environment, default_path)
+                    return super(TemplateLoader, self).get_source(
+                        environment, default_path
+                    )
             # if no default template is found, we re-raise the error
             raise e
 
@@ -147,15 +154,14 @@
 
 
 class ScriptsHandler(object):
-
     def __init__(self, renderer, template_path, template_root_dir, root_path):
         self.renderer = renderer
         self.template_root_dir = template_root_dir
         self.root_path = root_path
-        self.scripts = []  # we don't use a set because order may be important
+        self.scripts = []  #  we don't use a set because order may be important
         dummy, self.theme, self.is_default_theme = renderer.getThemeData(template_path)
 
-    def include(self, library_name, attribute='defer'):
+    def include(self, library_name, attribute="defer"):
         """Mark that a script need to be imported.
 
         Must be used before base.html is extended, as <script> are generated there.
@@ -163,13 +169,15 @@
         @param library_name(unicode): name of the library to import
         @param loading:
         """
-        if attribute not in ('defer', 'async', ''):
-            raise exceptions.DataError(_(u'Invalid attribute, please use one of "defer", "async" or ""'))
-        if library_name.endswith('.js'):
+        if attribute not in ("defer", "async", ""):
+            raise exceptions.DataError(
+                _(u'Invalid attribute, please use one of "defer", "async" or ""')
+            )
+        if library_name.endswith(".js"):
             library_name = library_name[:-3]
         if library_name not in self.scripts:
             self.scripts.append((library_name, attribute))
-        return u''
+        return u""
 
     def generate_scripts(self):
         """Generate the <script> elements
@@ -177,68 +185,72 @@
         @return (unicode): <scripts> HTML tags
         """
         scripts = []
-        tpl = u'<script src={src} {attribute}></script>'
+        tpl = u"<script src={src} {attribute}></script>"
         for library, attribute in self.scripts:
-            path = self.renderer.getStaticPath(library, self.template_root_dir, self.theme, self.is_default_theme, '.js')
+            path = self.renderer.getStaticPath(
+                library, self.template_root_dir, self.theme, self.is_default_theme, ".js"
+            )
             if path is None:
                 log.warning(_(u"Can't find {}.js javascript library").format(library))
                 continue
             path = os.path.join(self.root_path, path)
-            scripts.append(tpl.format(
-                src = quoteattr(path),
-                attribute = attribute,
-                ))
-        return safe(u'\n'.join(scripts))
+            scripts.append(tpl.format(src=quoteattr(path), attribute=attribute))
+        return safe(u"\n".join(scripts))
 
 
 class Renderer(object):
-
     def __init__(self, host):
         self.host = host
-        self.base_dir = os.path.dirname(sat_templates.__file__)  # FIXME: should be modified if we handle use extra dirs
+        self.base_dir = os.path.dirname(
+            sat_templates.__file__
+        )  # FIXME: should be modified if we handle use extra dirs
         self.env = jinja2.Environment(
             loader=TemplateLoader(),
-            autoescape=jinja2.select_autoescape(['html', 'xhtml', 'xml']),
+            autoescape=jinja2.select_autoescape(["html", "xhtml", "xml"]),
             trim_blocks=True,
             lstrip_blocks=True,
-            extensions=['jinja2.ext.i18n'],
-            )
+            extensions=["jinja2.ext.i18n"],
+        )
         self._locale_str = C.DEFAULT_LOCALE
         self._locale = Locale.parse(self._locale_str)
         self.installTranslations()
         # we want to have access to SàT constants in templates
-        self.env.globals[u'C'] = C
+        self.env.globals[u"C"] = C
         # custom filters
-        self.env.filters['next_gidx'] = self._next_gidx
-        self.env.filters['cur_gidx'] = self._cur_gidx
-        self.env.filters['date_fmt'] = self._date_fmt
-        self.env.filters['xmlui_class'] = self._xmlui_class
-        self.env.filters['attr_escape'] = self.attr_escape
-        self.env.filters['item_filter'] = self._item_filter
-        self.env.filters['adv_format'] = self._adv_format
-        self.env.filters['dict_ext'] = self._dict_ext
-        self.env.filters['highlight'] = self.highlight
+        self.env.filters["next_gidx"] = self._next_gidx
+        self.env.filters["cur_gidx"] = self._cur_gidx
+        self.env.filters["date_fmt"] = self._date_fmt
+        self.env.filters["xmlui_class"] = self._xmlui_class
+        self.env.filters["attr_escape"] = self.attr_escape
+        self.env.filters["item_filter"] = self._item_filter
+        self.env.filters["adv_format"] = self._adv_format
+        self.env.filters["dict_ext"] = self._dict_ext
+        self.env.filters["highlight"] = self.highlight
         # custom tests
-        self.env.tests['in_the_past'] = self._in_the_past
-        self.icons_path = os.path.join(host.media_dir, u'fonts/fontello/svg')
+        self.env.tests["in_the_past"] = self._in_the_past
+        self.icons_path = os.path.join(host.media_dir, u"fonts/fontello/svg")
 
     def installTranslations(self):
-        i18n_dir = os.path.join(self.base_dir, 'i18n')
+        i18n_dir = os.path.join(self.base_dir, "i18n")
         self.translations = {}
         for lang_dir in os.listdir(i18n_dir):
             lang_path = os.path.join(i18n_dir, lang_dir)
             if not os.path.isdir(lang_path):
                 continue
-            po_path = os.path.join(lang_path, 'LC_MESSAGES/sat.mo')
+            po_path = os.path.join(lang_path, "LC_MESSAGES/sat.mo")
             try:
-                with open(po_path, 'rb') as f:
-                    self.translations[Locale.parse(lang_dir)] = support.Translations(f, 'sat')
+                with open(po_path, "rb") as f:
+                    self.translations[Locale.parse(lang_dir)] = support.Translations(
+                        f, "sat"
+                    )
             except EnvironmentError:
-                log.error(_(u"Can't find template translation at {path}").format(path = po_path))
+                log.error(
+                    _(u"Can't find template translation at {path}").format(path=po_path)
+                )
             except UnknownLocaleError as e:
                 log.error(_(u"Invalid locale name: {msg}").format(msg=e))
             else:
-                log.info(_(u'loaded {lang} templates translations').format(lang=lang_dir))
+                log.info(_(u"loaded {lang} templates translations").format(lang=lang_dir))
         self.env.install_null_translations(True)
 
     def setLocale(self, locale_str):
@@ -248,10 +260,10 @@
         """
         if locale_str == self._locale_str:
             return
-        if locale_str == 'en':
+        if locale_str == "en":
             # we default to GB English when it's not specified
             # one of the main reason is to avoid the nonsense U.S. short date format
-            locale_str = 'en_GB'
+            locale_str = "en_GB"
         try:
             locale = Locale.parse(locale_str)
         except ValueError as e:
@@ -269,7 +281,7 @@
                 locale = Locale.parse(self._locale_str)
             else:
                 self.env.install_gettext_translations(translations, True)
-                log.debug(_(u'Switched to {lang}').format(lang=locale.english_name))
+                log.debug(_(u"Switched to {lang}").format(lang=locale.english_name))
 
         if locale_str == C.DEFAULT_LOCALE:
             self.env.install_null_translations(True)
@@ -286,7 +298,7 @@
         theme, dummy = self.env.loader.parse_template(template)
         return theme, os.path.join(self.base_dir, theme)
 
-    def getStaticPath(self, name, template_root_dir, theme, is_default, ext='.css'):
+    def getStaticPath(self, name, template_root_dir, theme, is_default, ext=".css"):
         """retrieve path of a static file if it exists with current theme or default
 
         File will be looked at [theme]/static/[name][ext], and then default
@@ -302,7 +314,9 @@
         if os.path.exists(os.path.join(template_root_dir, path)):
             file_ = path
         elif not is_default:
-            path = os.path.join(C.TEMPLATE_THEME_DEFAULT, C.TEMPLATE_STATIC_DIR, name + ext)
+            path = os.path.join(
+                C.TEMPLATE_THEME_DEFAULT, C.TEMPLATE_STATIC_DIR, name + ext
+            )
             if os.path.exists(os.path.join(template_root_dir, path)):
                 file_.append(path)
         return file_
@@ -315,7 +329,7 @@
             theme: theme of the page
             is_default: True if the theme is the default theme
         """
-        path_elems = [os.path.splitext(p)[0] for p in template_path.split(u'/')]
+        path_elems = [os.path.splitext(p)[0] for p in template_path.split(u"/")]
         theme = path_elems.pop(0)
         is_default = theme == C.TEMPLATE_THEME_DEFAULT
         return (path_elems, theme, is_default)
@@ -336,39 +350,44 @@
         # TODO: some caching would be nice
         css_files = []
         path_elems, theme, is_default = self.getThemeData(template_path)
-        for css in (u'fonts', u'styles'):
+        for css in (u"fonts", u"styles"):
             css_path = self.getStaticPath(css, template_root_dir, theme, is_default)
             if css_path is not None:
                 css_files.append(css_path)
 
         for idx, path in enumerate(path_elems):
-            css_path = self.getStaticPath(u'_'.join(path_elems[:idx+1]), template_root_dir, theme, is_default)
+            css_path = self.getStaticPath(
+                u"_".join(path_elems[: idx + 1]), template_root_dir, theme, is_default
+            )
             if css_path is not None:
                 css_files.append(css_path)
 
         return css_files
 
-
     ## custom filters ##
 
     @jinja2.contextfilter
     def _next_gidx(self, ctx, value):
         """Use next current global index as suffix"""
-        next_ = ctx['gidx'].next(value)
+        next_ = ctx["gidx"].next(value)
         return value if next_ == 0 else u"{}_{}".format(value, next_)
 
     @jinja2.contextfilter
     def _cur_gidx(self, ctx, value):
         """Use current current global index as suffix"""
-        current = ctx['gidx'].current(value)
+        current = ctx["gidx"].current(value)
         return value if not current else u"{}_{}".format(value, current)
 
-    def _date_fmt(self, timestamp, fmt='short', date_only=False, auto_limit=None, auto_old_fmt=None):
+    def _date_fmt(
+        self, timestamp, fmt="short", date_only=False, auto_limit=None, auto_old_fmt=None
+    ):
         if is_undefined(fmt):
-            fmt = u'short'
+            fmt = u"short"
 
         try:
-            return date_utils.date_fmt(timestamp, fmt, date_only, auto_limit, auto_old_fmt)
+            return date_utils.date_fmt(
+                timestamp, fmt, date_only, auto_limit, auto_old_fmt
+            )
         except Exception as e:
             log.warning(_(u"Can't parse date: {msg}").format(msg=e))
             return timestamp
@@ -378,7 +397,7 @@
 
         remove spaces, and put in lower case
         """
-        return RE_ATTR_ESCAPE.sub(u'_', text.strip().lower())[:50]
+        return RE_ATTR_ESCAPE.sub(u"_", text.strip().lower())[:50]
 
     def _xmlui_class(self, xmlui_item, fields):
         """return classes computed from XMLUI fields name
@@ -394,11 +413,13 @@
             escaped_name = self.attr_escape(name)
             try:
                 for value in xmlui_item.widgets[name].values:
-                    classes.append(escaped_name + '_' + self.attr_escape(value))
+                    classes.append(escaped_name + "_" + self.attr_escape(value))
             except KeyError:
-                log.debug(_(u"ignoring field \"{name}\": it doesn't exists").format(name=name))
+                log.debug(
+                    _(u'ignoring field "{name}": it doesn\'t exists').format(name=name)
+                )
                 continue
-        return u' '.join(classes) or None
+        return u" ".join(classes) or None
 
     @jinja2.contextfilter
     def _item_filter(self, ctx, item, filters):
@@ -420,8 +441,8 @@
         if filter_ is None:
             return value
         elif isinstance(filter_, dict):
-            filters_args = filter_.get(u'filters_args')
-            for idx, f_name in enumerate(filter_.get(u'filters', [])):
+            filters_args = filter_.get(u"filters_args")
+            for idx, f_name in enumerate(filter_.get(u"filters", [])):
                 kwargs = filters_args[idx] if filters_args is not None else {}
                 filter_func = self.env.filters[f_name]
                 try:
@@ -433,7 +454,7 @@
                     value = filter_func(ctx.eval_ctx, value, **kwargs)
                 else:
                     value = filter_func(value, **kwargs)
-            template = filter_.get(u'template')
+            template = filter_.get(u"template")
             if template:
                 # format will return a string, so we need to check first
                 # if the value is safe or not, and re-mark it after formatting
@@ -455,7 +476,7 @@
         """
         if template is None:
             return value
-        # jinja use string when no special char is used, so we have to convert to unicode
+        #  jinja use string when no special char is used, so we have to convert to unicode
         return unicode(template).format(value=value, **kwargs)
 
     def _dict_ext(self, source_dict, extra_dict, key=None):
@@ -511,29 +532,45 @@
 
     def _icon_defs(self, *names):
         """Define svg icons which will be used in the template, and use their name as id"""
-        svg_elt = etree.Element('svg', nsmap={None: 'http://www.w3.org/2000/svg'},
-                                width='0', height='0', style='display: block'
-                                )
-        defs_elt = etree.SubElement(svg_elt, 'defs')
+        svg_elt = etree.Element(
+            "svg",
+            nsmap={None: "http://www.w3.org/2000/svg"},
+            width="0",
+            height="0",
+            style="display: block",
+        )
+        defs_elt = etree.SubElement(svg_elt, "defs")
         for name in names:
-            path = os.path.join(self.icons_path, name + u'.svg')
+            path = os.path.join(self.icons_path, name + u".svg")
             icon_svg_elt = etree.parse(path).getroot()
             # we use icon name as id, so we can retrieve them easily
-            icon_svg_elt.set('id', name)
-            if not icon_svg_elt.tag == '{http://www.w3.org/2000/svg}svg':
-                raise exceptions.DataError(u'invalid SVG element')
+            icon_svg_elt.set("id", name)
+            if not icon_svg_elt.tag == "{http://www.w3.org/2000/svg}svg":
+                raise exceptions.DataError(u"invalid SVG element")
             defs_elt.append(icon_svg_elt)
-        return safe(etree.tostring(svg_elt, encoding='unicode'))
+        return safe(etree.tostring(svg_elt, encoding="unicode"))
 
-    def _icon_use(self, name, cls=''):
-        return safe(u"""<svg class="svg-icon{cls}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
+    def _icon_use(self, name, cls=""):
+        return safe(
+            u"""<svg class="svg-icon{cls}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
           <use href="#{name}"/>
         </svg>
         """.format(
-            name=name,
-            cls=(' ' + cls) if cls else ''))
+                name=name, cls=(" " + cls) if cls else ""
+            )
+        )
 
-    def render(self, template, theme=None, locale=C.DEFAULT_LOCALE, root_path=u'', media_path=u'', css_files=None, css_inline=False, **kwargs):
+    def render(
+        self,
+        template,
+        theme=None,
+        locale=C.DEFAULT_LOCALE,
+        root_path=u"",
+        media_path=u"",
+        css_files=None,
+        css_inline=False,
+        **kwargs
+    ):
         """render a template
 .
         @param template(unicode): template to render (e.g. blog/articles.html)
@@ -553,24 +590,28 @@
             raise ValueError(u"template can't be empty")
         if theme is not None:
             # use want to set a theme, we add it to the template path
-            if template[0] == u'(':
-                raise ValueError(u"you can't specify theme in template path and in argument at the same time")
-            elif template[0] == u'/':
+            if template[0] == u"(":
+                raise ValueError(
+                    u"you can't specify theme in template path and in argument at the same time"
+                )
+            elif template[0] == u"/":
                 raise ValueError(u"you can't specify theme with absolute paths")
-            template= u'(' + theme + u')' + template
+            template = u"(" + theme + u")" + template
         else:
             theme, dummy = self.env.loader.parse_template(template)
 
         template_source = self.env.get_template(template)
-        template_root_dir = os.path.normpath(self.base_dir)  # FIXME: should be modified if we handle use extra dirs
-        # XXX: template_path may have a different theme as first element than theme if a default page is used
-        template_path = template_source.filename[len(template_root_dir)+1:]
+        template_root_dir = os.path.normpath(
+            self.base_dir
+        )  # FIXME: should be modified if we handle use extra dirs
+        #  XXX: template_path may have a different theme as first element than theme if a default page is used
+        template_path = template_source.filename[len(template_root_dir) + 1 :]
 
         if css_files is None:
             css_files = self.getCSSFiles(template_path, template_root_dir)
 
-        kwargs['icon_defs'] = self._icon_defs
-        kwargs['icon'] = self._icon_use
+        kwargs["icon_defs"] = self._icon_defs
+        kwargs["icon"] = self._icon_use
 
         if css_inline:
             css_contents = []
@@ -579,13 +620,21 @@
                 with open(css_file_path) as f:
                     css_contents.append(f.read())
             if css_contents:
-                kwargs['css_content'] = '\n'.join(css_contents)
+                kwargs["css_content"] = "\n".join(css_contents)
 
-        scripts_handler = ScriptsHandler(self, template_path, template_root_dir, root_path)
+        scripts_handler = ScriptsHandler(
+            self, template_path, template_root_dir, root_path
+        )
         self.setLocale(locale)
         # XXX: theme used in template arguments is the requested theme, which may differ from actual theme
         #      if the template doesn't exist in the requested theme.
-        return template_source.render(theme=theme, root_path=root_path, media_path=media_path,
-                                      css_files=css_files, locale=self._locale,
-                                      gidx=Indexer(), script=scripts_handler,
-                                      **kwargs)
+        return template_source.render(
+            theme=theme,
+            root_path=root_path,
+            media_path=media_path,
+            css_files=css_files,
+            locale=self._locale,
+            gidx=Indexer(),
+            script=scripts_handler,
+            **kwargs
+        )
--- a/sat/tools/common/template_xmlui.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/tools/common/template_xmlui.py	Wed Jun 27 20:14:46 2018 +0200
@@ -23,14 +23,16 @@
 """
 
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from sat_frontends.tools import xmlui
 
 
 ## Widgets ##
 
+
 class Widget(object):
-    category = u'widget'
+    category = u"widget"
     type = None
     enabled = True
     read_only = True
@@ -44,7 +46,6 @@
 
 
 class ValueWidget(Widget):
-
     def __init__(self, xmlui_parent, value):
         super(ValueWidget, self).__init__(xmlui_parent)
         self.value = value
@@ -55,21 +56,19 @@
 
     @property
     def labels(self):
-        # helper property, there is not label for ValueWidget
+        #  helper property, there is not label for ValueWidget
         # but using labels make rendering more easy (one single method to call)
-        # values are actually returned
+        #  values are actually returned
         return [self.value]
 
 
 class InputWidget(ValueWidget):
-
     def __init__(self, xmlui_parent, value, read_only=False):
         super(InputWidget, self).__init__(xmlui_parent, value)
         self.read_only = read_only
 
 
 class OptionsWidget(Widget):
-
     def __init__(self, xmlui_parent, options, selected, style):
         super(OptionsWidget, self).__init__(xmlui_parent)
         self.options = options
@@ -91,21 +90,20 @@
     def items(self):
         """return suitable items, according to style"""
         no_select = self.no_select
-        for value,label in self.options:
+        for value, label in self.options:
             if no_select or value in self.selected:
-                yield value,label
+                yield value, label
 
     @property
     def inline(self):
-        return u'inline' in self.style
+        return u"inline" in self.style
 
     @property
     def no_select(self):
-        return u'noselect' in self.style
+        return u"noselect" in self.style
 
 
 class EmptyWidget(xmlui.EmptyWidget, Widget):
-
     def __init__(self, _xmlui_parent):
         Widget.__init__(self)
 
@@ -143,8 +141,9 @@
 
 ## Containers ##
 
+
 class Container(object):
-    category = u'container'
+    category = u"container"
     type = None
 
     def __init__(self, xmlui_parent):
@@ -162,26 +161,27 @@
 
 
 class VerticalContainer(xmlui.VerticalContainer, Container):
-    type = u'vertical'
+    type = u"vertical"
 
 
 class PairsContainer(xmlui.PairsContainer, Container):
-    type = u'pairs'
+    type = u"pairs"
 
 
 class LabelContainer(xmlui.PairsContainer, Container):
-    type = u'label'
+    type = u"label"
 
 
 ## Factory ##
 
+
 class WidgetFactory(object):
-
     def __getattr__(self, attr):
         if attr.startswith("create"):
             cls = globals()[attr[6:]]
             return cls
 
+
 ## Core ##
 
 
--- a/sat/tools/common/uri.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/tools/common/uri.py	Wed Jun 27 20:14:46 2018 +0200
@@ -24,6 +24,7 @@
 
 # FIXME: basic implementation, need to follow RFC 5122
 
+
 def parseXMPPUri(uri):
     """Parse an XMPP uri and return a dict with various information
 
@@ -39,57 +40,64 @@
         id: id of the element (item for pubsub)
     @raise ValueError: the scheme is not xmpp
     """
-    uri_split = urlparse.urlsplit(uri.encode('utf-8'))
-    if uri_split.scheme != 'xmpp':
-        raise ValueError(u'this is not a XMPP URI')
+    uri_split = urlparse.urlsplit(uri.encode("utf-8"))
+    if uri_split.scheme != "xmpp":
+        raise ValueError(u"this is not a XMPP URI")
 
     # XXX: we don't use jid.JID for path as it can be used both in backend and frontend
     # which may use different JID classes
-    data = {u'path': urllib.unquote(uri_split.path).decode('utf-8')}
+    data = {u"path": urllib.unquote(uri_split.path).decode("utf-8")}
 
-    query_end = uri_split.query.find(';')
+    query_end = uri_split.query.find(";")
     query_type = uri_split.query[:query_end]
-    if query_end == -1 or '=' in query_type:
-        raise ValueError('no query type, invalid XMPP URI')
+    if query_end == -1 or "=" in query_type:
+        raise ValueError("no query type, invalid XMPP URI")
 
     pairs = urlparse.parse_qs(uri_split.geturl())
     for k, v in pairs.items():
         if len(v) != 1:
             raise NotImplementedError(u"multiple values not managed")
-        if k in ('path', 'type', 'sub_type'):
+        if k in ("path", "type", "sub_type"):
             raise NotImplementedError(u"reserved key used in URI, this is not supported")
-        data[k.decode('utf-8')] = urllib.unquote(v[0]).decode('utf-8')
+        data[k.decode("utf-8")] = urllib.unquote(v[0]).decode("utf-8")
 
     if query_type:
-        data[u'type'] = query_type.decode('utf-8')
-    elif u'node' in data:
-        data[u'type'] = u'pubsub'
+        data[u"type"] = query_type.decode("utf-8")
+    elif u"node" in data:
+        data[u"type"] = u"pubsub"
     else:
-        data[u'type'] = ''
+        data[u"type"] = ""
 
-    if u'node' in data:
-        if data[u'node'].startswith(u'urn:xmpp:microblog:'):
-            data[u'sub_type'] = 'microblog'
+    if u"node" in data:
+        if data[u"node"].startswith(u"urn:xmpp:microblog:"):
+            data[u"sub_type"] = "microblog"
 
     return data
 
+
 def addPairs(uri, pairs):
-    for k,v in pairs.iteritems():
-        uri.append(u';' + urllib.quote_plus(k.encode('utf-8')) + u'=' + urllib.quote_plus(v.encode('utf-8')))
+    for k, v in pairs.iteritems():
+        uri.append(
+            u";"
+            + urllib.quote_plus(k.encode("utf-8"))
+            + u"="
+            + urllib.quote_plus(v.encode("utf-8"))
+        )
+
 
 def buildXMPPUri(type_, **kwargs):
-    uri = [u'xmpp:']
-    subtype = kwargs.pop('subtype', None)
-    path = kwargs.pop('path')
-    uri.append(urllib.quote_plus(path.encode('utf-8')).replace(u'%40', '@'))
+    uri = [u"xmpp:"]
+    subtype = kwargs.pop("subtype", None)
+    path = kwargs.pop("path")
+    uri.append(urllib.quote_plus(path.encode("utf-8")).replace(u"%40", "@"))
 
-    if type_ == u'pubsub':
-        if subtype == 'microblog' and not kwargs.get('node'):
-            kwargs[u'node'] = 'urn:xmpp:microblog:0'
+    if type_ == u"pubsub":
+        if subtype == "microblog" and not kwargs.get("node"):
+            kwargs[u"node"] = "urn:xmpp:microblog:0"
         if kwargs:
-            uri.append(u'?')
+            uri.append(u"?")
             addPairs(uri, kwargs)
     else:
-        raise NotImplementedError(u'{type_} URI are not handled yet'.format(type_=type_))
+        raise NotImplementedError(u"{type_} URI are not handled yet".format(type_=type_))
 
-    return u''.join(uri)
+    return u"".join(uri)
--- a/sat/tools/config.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/tools/config.py	Wed Jun 27 20:14:46 2018 +0200
@@ -21,6 +21,7 @@
 """ Configuration related useful methods """
 
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 
 from sat.core.constants import Const as C
@@ -50,23 +51,28 @@
         if not silent:
             log.debug(_(u"Testing file %s") % file_)
         if os.path.isfile(file_):
-            if file_.startswith(os.path.expanduser('~')):
+            if file_.startswith(os.path.expanduser("~")):
                 config.read([file_])
                 target_file = file_
             break
     if not target_file:
         # ... otherwise we create a new config file for that user
-        target_file = BaseDirectory.save_config_path('sat') + '/sat.conf'
+        target_file = BaseDirectory.save_config_path("sat") + "/sat.conf"
     if section and section.upper() != DEFAULTSECT and not config.has_section(section):
         config.add_section(section)
     config.set(section, option, value)
-    with open(target_file, 'wb') as configfile:
+    with open(target_file, "wb") as configfile:
         config.write(configfile)  # for the next time that user launches sat
     if not silent:
-        if option in ('passphrase',):  # list here the options storing a password
-            value = '******'
-        log.warning(_(u"Config auto-update: %(option)s set to %(value)s in the file %(config_file)s") %
-                    {'option': option, 'value': value, 'config_file': target_file})
+        if option in ("passphrase",):  # list here the options storing a password
+            value = "******"
+        log.warning(
+            _(
+                u"Config auto-update: %(option)s set to %(value)s in the file %(config_file)s"
+            )
+            % {"option": option, "value": value, "config_file": target_file}
+        )
+
 
 def parseMainConf():
     """look for main .ini configuration file, and parse it"""
@@ -77,6 +83,7 @@
         log.error(_("Can't read main config !"))
     return config
 
+
 def getConfig(config, section, name, default=None):
     """Get a configuration option
 
@@ -93,25 +100,29 @@
         section = DEFAULTSECT
 
     try:
-        value = config.get(section, name).decode('utf-8')
+        value = config.get(section, name).decode("utf-8")
     except (NoOptionError, NoSectionError) as e:
         if default is Exception:
             raise e
         return default
 
-    if name.endswith('_path') or name.endswith('_dir'):
+    if name.endswith("_path") or name.endswith("_dir"):
         value = os.path.expanduser(value)
     # thx to Brian (http://stackoverflow.com/questions/186857/splitting-a-semicolon-separated-string-to-a-dictionary-in-python/186873#186873)
-    elif name.endswith('_list'):
-        value = csv.reader([value], delimiter=',', quotechar='"', skipinitialspace=True).next()
-    elif name.endswith('_dict'):
+    elif name.endswith("_list"):
+        value = csv.reader(
+            [value], delimiter=",", quotechar='"', skipinitialspace=True
+        ).next()
+    elif name.endswith("_dict"):
         try:
             value = json.loads(value)
         except ValueError as e:
             raise exceptions.ParsingError(u"Error while parsing data: {}".format(e))
         if not isinstance(value, dict):
-            raise exceptions.ParsingError(u"{name} value is not a dict: {value}".format(name=name, value=value))
-    elif name.endswith('_json'):
+            raise exceptions.ParsingError(
+                u"{name} value is not a dict: {value}".format(name=name, value=value)
+            )
+    elif name.endswith("_json"):
         try:
             value = json.loads(value)
         except ValueError as e:
--- a/sat/tools/email.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/tools/email.py	Wed Jun 27 20:14:46 2018 +0200
@@ -22,13 +22,13 @@
 from __future__ import absolute_import
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from twisted.mail import smtp
 from email.mime.text import MIMEText
 
 
-
-def sendEmail(host, to_emails, subject=u'', body=u'', from_email=None):
+def sendEmail(host, to_emails, subject=u"", body=u"", from_email=None):
     """send an email using SàT configuration
 
     @param to_emails(list[unicode], unicode): list of recipients
@@ -40,31 +40,35 @@
     """
     if isinstance(to_emails, basestring):
         to_emails = to_emails.split()
-    email_host = host.memory.getConfig(None, u'email_server') or u'localhost'
-    email_from = host.memory.getConfig(None, u'email_from')
+    email_host = host.memory.getConfig(None, u"email_server") or u"localhost"
+    email_from = host.memory.getConfig(None, u"email_from")
     if email_from is None:
         # we suppose that email domain and XMPP domain are identical
-        domain = host.memory.getConfig(None, u'xmpp_domain', u'example.net')
-        email_from = u'no_reply@' + domain
-    email_sender_domain = host.memory.getConfig(None, u'email_sender_domain')
-    email_port = int(host.memory.getConfig(None, u'email_port', 25))
-    email_username = host.memory.getConfig(None, u'email_username')
-    email_password = host.memory.getConfig(None, u'email_password')
-    email_auth = C.bool(host.memory.getConfig(None, 'email_auth', False))
-    email_starttls = C.bool(host.memory.getConfig(None, 'email_starttls', False))
+        domain = host.memory.getConfig(None, u"xmpp_domain", u"example.net")
+        email_from = u"no_reply@" + domain
+    email_sender_domain = host.memory.getConfig(None, u"email_sender_domain")
+    email_port = int(host.memory.getConfig(None, u"email_port", 25))
+    email_username = host.memory.getConfig(None, u"email_username")
+    email_password = host.memory.getConfig(None, u"email_password")
+    email_auth = C.bool(host.memory.getConfig(None, "email_auth", False))
+    email_starttls = C.bool(host.memory.getConfig(None, "email_starttls", False))
+
+    msg = MIMEText(body, "plain", "UTF-8")
+    msg[u"Subject"] = subject
+    msg[u"From"] = email_from
+    msg[u"To"] = u", ".join(to_emails)
 
-    msg = MIMEText(body, 'plain', 'UTF-8')
-    msg[u'Subject'] = subject
-    msg[u'From'] = email_from
-    msg[u'To'] = u", ".join(to_emails)
-
-    return smtp.sendmail(email_host.encode("utf-8"),
-                         email_from.encode("utf-8"),
-                         [email.encode("utf-8") for email in to_emails],
-                         msg.as_string(),
-                         senderDomainName = email_sender_domain.encode("utf-8") if email_sender_domain else None,
-                         port = email_port,
-                         username = email_username.encode("utf-8") if email_username else None,
-                         password = email_password.encode("utf-8") if email_password else None,
-                         requireAuthentication = email_auth,
-                         requireTransportSecurity = email_starttls)
+    return smtp.sendmail(
+        email_host.encode("utf-8"),
+        email_from.encode("utf-8"),
+        [email.encode("utf-8") for email in to_emails],
+        msg.as_string(),
+        senderDomainName=email_sender_domain.encode("utf-8")
+        if email_sender_domain
+        else None,
+        port=email_port,
+        username=email_username.encode("utf-8") if email_username else None,
+        password=email_password.encode("utf-8") if email_password else None,
+        requireAuthentication=email_auth,
+        requireTransportSecurity=email_starttls,
+    )
--- a/sat/tools/sat_defer.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/tools/sat_defer.py	Wed Jun 27 20:14:46 2018 +0200
@@ -20,6 +20,7 @@
 """tools related to deferred"""
 
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from sat.core import exceptions
 from twisted.internet import defer
@@ -29,8 +30,8 @@
 from sat.core.constants import Const as C
 from sat.memory import memory
 
-KEY_DEFERREDS = 'deferreds'
-KEY_NEXT = 'next_defer'
+KEY_DEFERREDS = "deferreds"
+KEY_NEXT = "next_defer"
 
 
 class DelayedDeferred(object):
@@ -52,19 +53,19 @@
         self._deferred.cancel()
 
     def addCallbacks(self, *args, **kwargs):
-        self._deferred.addCallbacks(*args,**kwargs)
+        self._deferred.addCallbacks(*args, **kwargs)
 
     def addCallback(self, *args, **kwargs):
-        self._deferred.addCallback(*args,**kwargs)
+        self._deferred.addCallback(*args, **kwargs)
 
     def addErrback(self, *args, **kwargs):
-        self._deferred.addErrback(*args,**kwargs)
+        self._deferred.addErrback(*args, **kwargs)
 
     def addBoth(self, *args, **kwargs):
-        self._deferred.addBoth(*args,**kwargs)
+        self._deferred.addBoth(*args, **kwargs)
 
     def chainDeferred(self, *args, **kwargs):
-        self._deferred.chainDeferred(*args,**kwargs)
+        self._deferred.chainDeferred(*args, **kwargs)
 
     def pause(self):
         self._deferred.pause()
@@ -76,13 +77,14 @@
 class RTDeferredSessions(memory.Sessions):
     """Real Time Deferred Sessions"""
 
-
     def __init__(self, timeout=120):
         """Manage list of Deferreds in real-time, allowing to get intermediate results
 
         @param timeout (int): nb of seconds before deferreds cancellation
         """
-        super(RTDeferredSessions, self).__init__(timeout=timeout, resettable_timeout=False)
+        super(RTDeferredSessions, self).__init__(
+            timeout=timeout, resettable_timeout=False
+        )
 
     def newSession(self, deferreds, profile):
         """Launch a new session with a list of deferreds
@@ -92,7 +94,9 @@
         @param return (tupe[str, defer.Deferred]): tuple with session id and a deferred wich fire *WITHOUT RESULT* when all results are received
         """
         data = {KEY_NEXT: defer.Deferred()}
-        session_id, session_data = super(RTDeferredSessions, self).newSession(data, profile=profile)
+        session_id, session_data = super(RTDeferredSessions, self).newSession(
+            data, profile=profile
+        )
         if isinstance(deferreds, dict):
             session_data[KEY_DEFERREDS] = deferreds.values()
             iterator = deferreds.iteritems()
@@ -107,7 +111,9 @@
             d.addErrback(self._errback, d, session_id, profile)
         return session_id
 
-    def _purgeSession(self, session_id, reason=u"timeout", no_warning=False, got_result=False):
+    def _purgeSession(
+        self, session_id, reason=u"timeout", no_warning=False, got_result=False
+    ):
         """Purge the session
 
         @param session_id(str): id of the session to purge
@@ -121,7 +127,9 @@
             try:
                 timer, session_data, profile = self._sessions[session_id]
             except ValueError:
-                raise exceptions.InternalError(u'was expecting timer, session_data and profile; is profile set ?')
+                raise exceptions.InternalError(
+                    u"was expecting timer, session_data and profile; is profile set ?"
+                )
 
             # next_defer must be called before deferreds,
             # else its callback will be called by _gotResult
@@ -134,7 +142,9 @@
                 d.cancel()
 
             if not no_warning:
-                log.warning(u"RTDeferredList cancelled: {} (profile {})".format(reason, profile))
+                log.warning(
+                    u"RTDeferredList cancelled: {} (profile {})".format(reason, profile)
+                )
 
         super(RTDeferredSessions, self)._purgeSession(session_id)
 
@@ -165,7 +175,9 @@
         """
         self._purgeSession(session_id, reason=reason, no_warning=no_log)
 
-    def getResults(self, session_id, on_success=None, on_error=None, profile=C.PROF_KEY_NONE):
+    def getResults(
+        self, session_id, on_success=None, on_error=None, profile=C.PROF_KEY_NONE
+    ):
         """Get current results of a real-time deferred session
 
         result already gotten are deleted
@@ -197,11 +209,13 @@
         def next_cb(dummy):
             # we got one or several results
             results = {}
-            filtered_data = [] # used to keep deferreds without results
+            filtered_data = []  # used to keep deferreds without results
             deferreds = session_data[KEY_DEFERREDS]
 
             for d in deferreds:
-                if d._RTDeferred_return: # we don't use d.called as called is True before the full callbacks chain has been called
+                if (
+                    d._RTDeferred_return
+                ):  # we don't use d.called as called is True before the full callbacks chain has been called
                     # we have a result
                     idx = d._RTDeferred_index
                     success, result = d._RTDeferred_return
@@ -210,7 +224,9 @@
                             if callable(on_success):
                                 result = yield on_success(result)
                             else:
-                                raise exceptions.InternalError('Unknown value of on_success: {}'.format(on_success))
+                                raise exceptions.InternalError(
+                                    "Unknown value of on_success: {}".format(on_success)
+                                )
 
                     else:
                         if on_error is not None:
@@ -219,7 +235,9 @@
                             elif callable(on_error):
                                 result = yield on_error(result)
                             else:
-                                raise exceptions.InternalError('Unknown value of on_error: {}'.format(on_error))
+                                raise exceptions.InternalError(
+                                    "Unknown value of on_error: {}".format(on_error)
+                                )
                     results[idx] = (success, result)
                 else:
                     filtered_data.append(d)
--- a/sat/tools/stream.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/tools/stream.py	Wed Jun 27 20:14:46 2018 +0200
@@ -32,7 +32,6 @@
 
 
 class IStreamProducer(interface.Interface):
-
     def startStream(consumer):
         """start producing the stream
 
@@ -42,9 +41,20 @@
 
 class SatFile(object):
     """A file-like object to have high level files manipulation"""
+
     # TODO: manage "with" statement
 
-    def __init__(self, host, client, path, mode='rb', uid=None, size=None, data_cb=None, auto_end_signals=True):
+    def __init__(
+        self,
+        host,
+        client,
+        path,
+        mode="rb",
+        uid=None,
+        size=None,
+        data_cb=None,
+        auto_end_signals=True,
+    ):
         """
         @param host: %(doc_host)s
         @param path(str): path of the file to get
@@ -67,7 +77,9 @@
         self.data_cb = data_cb
         self.auto_end_signals = auto_end_signals
         metadata = self.getProgressMetadata()
-        self.host.registerProgressCb(self.uid, self.getProgress, metadata, profile=client.profile)
+        self.host.registerProgressCb(
+            self.uid, self.getProgress, metadata, profile=client.profile
+        )
         self.host.bridge.progressStarted(self.uid, metadata, client.profile)
 
     def checkSize(self):
@@ -91,14 +103,14 @@
             error can happen even if error is None, if current size differ from given size
         """
         if self._file.closed:
-            return # avoid double close (which is allowed) error
+            return  # avoid double close (which is allowed) error
         if error is None:
             try:
                 size_ok = self.checkSize()
             except exceptions.NotFound:
                 size_ok = True
             if not size_ok:
-                error = u'declared and actual size mismatch'
+                error = u"declared and actual size mismatch"
                 log.warning(error)
                 progress_metadata = None
 
@@ -149,35 +161,34 @@
 
         @return (dict): metadata (check bridge for documentation)
         """
-        metadata = {'type': C.META_TYPE_FILE}
+        metadata = {"type": C.META_TYPE_FILE}
 
         mode = self._file.mode
-        if '+' in mode:
-            pass # we have no direction in read/write modes
-        elif mode in ('r', 'rb'):
-            metadata['direction'] = 'out'
-        elif mode in ('w', 'wb'):
-            metadata['direction'] = 'in'
-        elif 'U' in mode:
-            metadata['direction'] = 'out'
+        if "+" in mode:
+            pass  # we have no direction in read/write modes
+        elif mode in ("r", "rb"):
+            metadata["direction"] = "out"
+        elif mode in ("w", "wb"):
+            metadata["direction"] = "in"
+        elif "U" in mode:
+            metadata["direction"] = "out"
         else:
             raise exceptions.InternalError
 
-        metadata['name'] = self._file.name
+        metadata["name"] = self._file.name
 
         return metadata
 
     def getProgress(self, progress_id, profile):
-        ret = {'position': self._file.tell()}
+        ret = {"position": self._file.tell()}
         if self.size:
-            ret['size'] = self.size
+            ret["size"] = self.size
         return ret
 
 
 @interface.implementer(IStreamProducer)
 @interface.implementer(interfaces.IConsumer)
 class FileStreamObject(basic.FileSender):
-
     def __init__(self, host, client, path, **kwargs):
         """
 
--- a/sat/tools/trigger.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/tools/trigger.py	Wed Jun 27 20:14:46 2018 +0200
@@ -21,6 +21,7 @@
 
 from sat.core.i18n import _
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 
 
@@ -30,6 +31,7 @@
 
 class SkipOtherTriggers(Exception):
     """ Exception to raise if normal behaviour must be followed instead of following triggers list """
+
     pass
 
 
@@ -37,8 +39,8 @@
     """This class manage triggers: code which interact to change the behaviour of SàT"""
 
     try:  # FIXME: to be removed when a better solution is found
-        MIN_PRIORITY = float('-inf')
-        MAX_PRIORITY = float('+inf')
+        MIN_PRIORITY = float("-inf")
+        MAX_PRIORITY = float("+inf")
     except:  # XXX: Pyjamas will bug if you specify ValueError here
         # Pyjamas uses the JS Float class
         MIN_PRIORITY = Number.NEGATIVE_INFINITY
@@ -57,14 +59,20 @@
         """
         if point_name not in self.__triggers:
             self.__triggers[point_name] = []
-        if priority != 0 and priority in [trigger_tuple[0] for trigger_tuple in self.__triggers[point_name]]:
+        if priority != 0 and priority in [
+            trigger_tuple[0] for trigger_tuple in self.__triggers[point_name]
+        ]:
             if priority in (self.MIN_PRIORITY, self.MAX_PRIORITY):
                 log.warning(_(u"There is already a bound priority [%s]") % point_name)
             else:
-                log.debug(_(u"There is already a trigger with the same priority [%s]") % point_name)
+                log.debug(
+                    _(u"There is already a trigger with the same priority [%s]")
+                    % point_name
+                )
         self.__triggers[point_name].append((priority, callback))
-        self.__triggers[point_name].sort(key=lambda trigger_tuple:
-                                         trigger_tuple[0], reverse=True)
+        self.__triggers[point_name].sort(
+            key=lambda trigger_tuple: trigger_tuple[0], reverse=True
+        )
 
     def remove(self, point_name, callback):
         """Remove a trigger from a point
--- a/sat/tools/utils.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/tools/utils.py	Wed Jun 27 20:14:46 2018 +0200
@@ -23,6 +23,7 @@
 import os.path
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 import datetime
 from twisted.python import procutils
@@ -35,7 +36,7 @@
 import functools
 
 
-NO_REPOS_DATA = u'repository data unknown'
+NO_REPOS_DATA = u"repository data unknown"
 repos_cache_dict = None
 repos_cache = None
 
@@ -45,12 +46,15 @@
 
     remove special characters from unicode string
     """
+
     def valid_chars(unicode_source):
         for char in unicode_source:
-            if unicodedata.category(char) == 'Cc' and char!='\n':
+            if unicodedata.category(char) == "Cc" and char != "\n":
                 continue
             yield char
-    return ''.join(valid_chars(ustr))
+
+    return "".join(valid_chars(ustr))
+
 
 def partial(func, *fixed_args, **fixed_kwargs):
     # FIXME: temporary hack to workaround the fact that inspect.getargspec is not working with functools.partial
@@ -59,22 +63,27 @@
 
     ori_args = inspect.getargspec(func).args
     func = functools.partial(func, *fixed_args, **fixed_kwargs)
-    if ori_args[0] == 'self':
+    if ori_args[0] == "self":
         del ori_args[0]
-    ori_args = ori_args[len(fixed_args):]
+    ori_args = ori_args[len(fixed_args) :]
     for kw in fixed_kwargs:
         ori_args.remove(kw)
 
-    exec(textwrap.dedent('''\
+    exec(
+        textwrap.dedent(
+            """\
     def method({args}):
         return func({kw_args})
-    ''').format(
-        args = ', '.join(ori_args),
-        kw_args = ', '.join([a+'='+a for a in ori_args]))
-    , locals())
+    """
+        ).format(
+            args=", ".join(ori_args), kw_args=", ".join([a + "=" + a for a in ori_args])
+        ),
+        locals(),
+    )
 
     return method
 
+
 def xmpp_date(timestamp=None, with_time=True):
     """Return date according to XEP-0082 specification
 
@@ -86,8 +95,13 @@
     """
     template_date = u"%Y-%m-%d"
     template_time = u"%H:%M:%SZ"
-    template = u"{}T{}".format(template_date, template_time) if with_time else template_date
-    return datetime.datetime.utcfromtimestamp(time.time() if timestamp is None else timestamp).strftime(template)
+    template = (
+        u"{}T{}".format(template_date, template_time) if with_time else template_date
+    )
+    return datetime.datetime.utcfromtimestamp(
+        time.time() if timestamp is None else timestamp
+    ).strftime(template)
+
 
 def generatePassword(vocabulary=None, size=20):
     """Generate a password with random characters.
@@ -98,8 +112,11 @@
     """
     random.seed()
     if vocabulary is None:
-        vocabulary = [chr(i) for i in range(0x30,0x3A) + range(0x41,0x5B) + range (0x61,0x7B)]
-    return u''.join([random.choice(vocabulary) for i in range(15)])
+        vocabulary = [
+            chr(i) for i in range(0x30, 0x3A) + range(0x41, 0x5B) + range(0x61, 0x7B)
+        ]
+    return u"".join([random.choice(vocabulary) for i in range(15)])
+
 
 def getRepositoryData(module, as_string=True, is_path=False):
     """Retrieve info on current mecurial repository
@@ -133,11 +150,11 @@
             return repos_cache_dict
 
     if sys.platform == "android":
-        # FIXME: workaround to avoid trouble on android, need to be fixed properly
+        #  FIXME: workaround to avoid trouble on android, need to be fixed properly
         repos_cache = u"Cagou android build"
         return repos_cache
 
-    KEYS=("node", "node_short", "branch", "date", "tag", "distance")
+    KEYS = ("node", "node_short", "branch", "date", "tag", "distance")
     ori_cwd = os.getcwd()
 
     if is_path:
@@ -146,7 +163,7 @@
         repos_root = os.path.abspath(os.path.dirname(module.__file__))
 
     try:
-        hg_path = procutils.which('hg')[0]
+        hg_path = procutils.which("hg")[0]
     except IndexError:
         log.warning(u"Can't find hg executable")
         hg_path = None
@@ -155,18 +172,27 @@
     if hg_path is not None:
         os.chdir(repos_root)
         try:
-            hg_data_raw = subprocess.check_output(["hg","log", "-r", "-1", "--template","{node}\n"
-                                                                                        "{node|short}\n"
-                                                                                        "{branch}\n"
-                                                                                        "{date|isodate}\n"
-                                                                                        "{latesttag}\n"
-                                                                                        "{latesttagdistance}"])
+            hg_data_raw = subprocess.check_output(
+                [
+                    "hg",
+                    "log",
+                    "-r",
+                    "-1",
+                    "--template",
+                    "{node}\n"
+                    "{node|short}\n"
+                    "{branch}\n"
+                    "{date|isodate}\n"
+                    "{latesttag}\n"
+                    "{latesttagdistance}",
+                ]
+            )
         except subprocess.CalledProcessError:
             hg_data = {}
         else:
-            hg_data = dict(zip(KEYS, hg_data_raw.split('\n')))
+            hg_data = dict(zip(KEYS, hg_data_raw.split("\n")))
             try:
-                hg_data['modified'] = '+' in subprocess.check_output(["hg","id","-i"])
+                hg_data["modified"] = "+" in subprocess.check_output(["hg", "id", "-i"])
             except subprocess.CalledProcessError:
                 pass
     else:
@@ -180,9 +206,9 @@
         else:
             os.chdir(os.path.abspath(os.path.dirname(repos_root)))
         try:
-            with open('.hg/dirstate') as hg_dirstate:
-                hg_data['node'] = hg_dirstate.read(20).encode('hex')
-                hg_data['node_short'] = hg_data['node'][:12]
+            with open(".hg/dirstate") as hg_dirstate:
+                hg_data["node"] = hg_dirstate.read(20).encode("hex")
+                hg_data["node_short"] = hg_data["node"][:12]
         except IOError:
             log.debug(u"Can't access repository data")
 
@@ -193,25 +219,33 @@
         log.debug(u"Mercurial not available or working, trying package version")
         try:
             import pkg_resources
+
             pkg_version = pkg_resources.get_distribution(C.APP_NAME_FILE).version
-            version, local_id = pkg_version.split('+', 1)
+            version, local_id = pkg_version.split("+", 1)
         except ImportError:
             log.warning("pkg_resources not available, can't get package data")
         except pkg_resources.DistributionNotFound:
             log.warning("can't retrieve package data")
         except ValueError:
-            log.info(u"no local version id in package: {pkg_version}".format(pkg_version=pkg_version))
+            log.info(
+                u"no local version id in package: {pkg_version}".format(
+                    pkg_version=pkg_version
+                )
+            )
         else:
-            version = version.replace('.dev0', 'D')
+            version = version.replace(".dev0", "D")
             if version != C.APP_VERSION:
-                log.warning("Incompatible version ({version}) and pkg_version ({pkg_version})".format(
-                    version=C.APP_VERSION, pkg_version=pkg_version))
+                log.warning(
+                    "Incompatible version ({version}) and pkg_version ({pkg_version})".format(
+                        version=C.APP_VERSION, pkg_version=pkg_version
+                    )
+                )
             else:
                 try:
-                    hg_node, hg_distance = local_id.split('.')
+                    hg_node, hg_distance = local_id.split(".")
                 except ValueError:
                     log.warning("Version doesn't specify repository data")
-                hg_data = {'node_short': hg_node, 'distance': hg_distance}
+                hg_data = {"node_short": hg_node, "distance": hg_distance}
 
     repos_cache_dict = hg_data
 
@@ -219,21 +253,21 @@
         if not hg_data:
             repos_cache = NO_REPOS_DATA
         else:
-            strings = [u'rev', hg_data['node_short']]
+            strings = [u"rev", hg_data["node_short"]]
             try:
-                if hg_data['modified']:
+                if hg_data["modified"]:
                     strings.append(u"[M]")
             except KeyError:
                 pass
             try:
-                strings.extend([u'({branch} {date})'.format(**hg_data)])
+                strings.extend([u"({branch} {date})".format(**hg_data)])
             except KeyError:
                 pass
             try:
-                strings.extend([u'+{distance}'.format(**hg_data)])
+                strings.extend([u"+{distance}".format(**hg_data)])
             except KeyError:
                 pass
-            repos_cache = u' '.join(strings)
+            repos_cache = u" ".join(strings)
         return repos_cache
     else:
         return hg_data
--- a/sat/tools/xml_tools.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/tools/xml_tools.py	Wed Jun 27 20:14:46 2018 +0200
@@ -20,6 +20,7 @@
 from sat.core.i18n import _
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 
 from xml.dom import minidom, NotFoundErr
@@ -37,14 +38,15 @@
 
 SAT_FORM_PREFIX = "SAT_FORM_"
 SAT_PARAM_SEPARATOR = "_XMLUI_PARAM_"  # used to have unique elements names
-html_entity_re = re.compile(r'&([a-zA-Z]+?);')
-XML_ENTITIES = ('quot', 'amp', 'apos', 'lt', 'gt')
+html_entity_re = re.compile(r"&([a-zA-Z]+?);")
+XML_ENTITIES = ("quot", "amp", "apos", "lt", "gt")
 
 # TODO: move XMLUI stuff in a separate module
 # TODO: rewrite this with lxml or ElementTree or domish.Element: it's complicated and difficult to maintain with current minidom implementation
 
 # Helper functions
 
+
 def _dataFormField2XMLUIData(field, read_only=False):
     """Get data needed to create an XMLUI's Widget from Wokkel's data_form's Field.
 
@@ -55,8 +57,8 @@
     """
     widget_args = [field.value]
     widget_kwargs = {}
-    if field.fieldType == 'fixed' or field.fieldType is None:
-        widget_type = 'text'
+    if field.fieldType == "fixed" or field.fieldType is None:
+        widget_type = "text"
         if field.value is None:
             if field.label is None:
                 log.warning(_("Fixed field has neither value nor label, ignoring it"))
@@ -65,36 +67,40 @@
                 field.value = field.label
                 field.label = None
             widget_args[0] = field.value
-    elif field.fieldType == 'text-single':
+    elif field.fieldType == "text-single":
         widget_type = "string"
-        widget_kwargs['read_only'] = read_only
-    elif field.fieldType == 'jid-single':
+        widget_kwargs["read_only"] = read_only
+    elif field.fieldType == "jid-single":
         widget_type = "jid_input"
-        widget_kwargs['read_only'] = read_only
-    elif field.fieldType == 'text-multi':
+        widget_kwargs["read_only"] = read_only
+    elif field.fieldType == "text-multi":
         widget_type = "textbox"
-        widget_args[0] = u'\n'.join(field.values)
-        widget_kwargs['read_only'] = read_only
-    elif field.fieldType == 'text-private':
+        widget_args[0] = u"\n".join(field.values)
+        widget_kwargs["read_only"] = read_only
+    elif field.fieldType == "text-private":
         widget_type = "password"
-        widget_kwargs['read_only'] = read_only
-    elif field.fieldType == 'boolean':
+        widget_kwargs["read_only"] = read_only
+    elif field.fieldType == "boolean":
         widget_type = "bool"
         if widget_args[0] is None:
-            widget_args[0] = 'false'
-        widget_kwargs['read_only'] = read_only
-    elif field.fieldType == 'integer':
+            widget_args[0] = "false"
+        widget_kwargs["read_only"] = read_only
+    elif field.fieldType == "integer":
         widget_type = "integer"
-        widget_kwargs['read_only'] = read_only
-    elif field.fieldType == 'list-single':
+        widget_kwargs["read_only"] = read_only
+    elif field.fieldType == "list-single":
         widget_type = "list"
-        widget_kwargs["options"] = [(option.value, option.label or option.value) for option in field.options]
+        widget_kwargs["options"] = [
+            (option.value, option.label or option.value) for option in field.options
+        ]
         widget_kwargs["selected"] = widget_args
         widget_args = []
     else:
-        log.error(u"FIXME FIXME FIXME: Type [%s] is not managed yet by SàT" % field.fieldType)
+        log.error(
+            u"FIXME FIXME FIXME: Type [%s] is not managed yet by SàT" % field.fieldType
+        )
         widget_type = "string"
-        widget_kwargs['read_only'] = read_only
+        widget_kwargs["read_only"] = read_only
 
     if field.var:
         widget_kwargs["name"] = field.var
@@ -121,7 +127,7 @@
     if filters is None:
         filters = {}
     if form.instructions:
-        form_ui.addText('\n'.join(form.instructions), 'instructions')
+        form_ui.addText("\n".join(form.instructions), "instructions")
 
     form_ui.changeContainer("label")
 
@@ -130,13 +136,17 @@
             form_ui.addWidget(*widget_args)
 
     for field in form.fieldList:
-        widget_type, widget_args, widget_kwargs = _dataFormField2XMLUIData(field, read_only)
+        widget_type, widget_args, widget_kwargs = _dataFormField2XMLUIData(
+            field, read_only
+        )
         try:
-            widget_filter = filters[widget_kwargs['name']]
+            widget_filter = filters[widget_kwargs["name"]]
         except KeyError:
             pass
         else:
-            widget_type, widget_args, widget_kwargs = widget_filter(form_ui, widget_type, widget_args, widget_kwargs)
+            widget_type, widget_args, widget_kwargs = widget_filter(
+                form_ui, widget_type, widget_args, widget_kwargs
+            )
         label = field.label or field.var
         if label:
             form_ui.addLabel(label)
@@ -172,27 +182,29 @@
     """
     headers = OrderedDict()
     try:
-        reported_elt = form_xml.elements('jabber:x:data', 'reported').next()
+        reported_elt = form_xml.elements("jabber:x:data", "reported").next()
     except StopIteration:
-        raise exceptions.DataError("Couldn't find expected <reported> tag in %s" % form_xml.toXml())
+        raise exceptions.DataError(
+            "Couldn't find expected <reported> tag in %s" % form_xml.toXml()
+        )
 
     for elt in reported_elt.elements():
         if elt.name != "field":
             raise exceptions.DataError("Unexpected tag")
         name = elt["var"]
-        label = elt.attributes.get('label', '')
-        type_ = elt.attributes.get('type')
+        label = elt.attributes.get("label", "")
+        type_ = elt.attributes.get("type")
         headers[name] = (label, type_)
 
     if not headers:
         raise exceptions.DataError("No reported fields (see XEP-0004 §3.4)")
 
     xmlui_data = []
-    item_elts = form_xml.elements('jabber:x:data', 'item')
+    item_elts = form_xml.elements("jabber:x:data", "item")
 
     for item_elt in item_elts:
         for elt in item_elt.elements():
-            if elt.name != 'field':
+            if elt.name != "field":
                 log.warning(u"Unexpected tag (%s)" % elt.name)
                 continue
             field = data_form.Field.fromElement(elt)
@@ -211,7 +223,9 @@
     @param xmlui_data (list[tuple]): list of (widget_type, widget_args, widget_kwargs)
     @return: the completed XMLUI instance
     """
-    adv_list = AdvancedListContainer(xmlui, headers=headers, columns=len(headers), parent=xmlui.current_container)
+    adv_list = AdvancedListContainer(
+        xmlui, headers=headers, columns=len(headers), parent=xmlui.current_container
+    )
     xmlui.changeContainer(adv_list)
 
     for widget_type, widget_args, widget_kwargs in xmlui_data:
@@ -248,7 +262,10 @@
         dataForm2Widgets(xml_ui, parsed_form, read_only=True)
     return xml_ui
 
-def dataFormResult2XMLUI(result_form, base_form, session_id=None, prepend=None, filters=None):
+
+def dataFormResult2XMLUI(
+    result_form, base_form, session_id=None, prepend=None, filters=None
+):
     """Convert data form result to SàT XMLUI.
 
     @param result_form (data_form.Form): result form to convert
@@ -290,7 +307,11 @@
     @param xmlui_data (dict): data returned by frontends for XMLUI form
     @return: dict of data usable by Wokkel's data form
     """
-    return {key[len(SAT_FORM_PREFIX):]: _cleanValue(value) for key, value in xmlui_data.iteritems() if key.startswith(SAT_FORM_PREFIX)}
+    return {
+        key[len(SAT_FORM_PREFIX) :]: _cleanValue(value)
+        for key, value in xmlui_data.iteritems()
+        if key.startswith(SAT_FORM_PREFIX)
+    }
 
 
 def formEscape(name):
@@ -308,7 +329,7 @@
     @param xmlui_data (dict): data returned by frontends for XMLUI form
     @return: domish.Element
     """
-    form = data_form.Form('submit')
+    form = data_form.Form("submit")
     form.makeFields(XMLUIResult2DataFormResult(xmlui_data))
     return form.toElement()
 
@@ -319,7 +340,7 @@
     @param values (list): list of tuples
     @return: data_form.Form
     """
-    form = data_form.Form('submit')
+    form = data_form.Form("submit")
     for value in values:
         field = data_form.Field(var=value[0], value=value[1])
         form.addField(field)
@@ -334,39 +355,41 @@
     @return: XMLUI
     """
     # TODO: refactor params and use Twisted directly to parse XML
-    params_doc = minidom.parseString(xml.encode('utf-8'))
+    params_doc = minidom.parseString(xml.encode("utf-8"))
     top = params_doc.documentElement
-    if top.nodeName != 'params':
-        raise exceptions.DataError(_('INTERNAL ERROR: parameters xml not valid'))
+    if top.nodeName != "params":
+        raise exceptions.DataError(_("INTERNAL ERROR: parameters xml not valid"))
 
     param_ui = XMLUI("param", "tabs")
     tabs_cont = param_ui.current_container
 
     for category in top.getElementsByTagName("category"):
-        category_name = category.getAttribute('name')
-        label = category.getAttribute('label')
+        category_name = category.getAttribute("name")
+        label = category.getAttribute("label")
         if not category_name:
-            raise exceptions.DataError(_('INTERNAL ERROR: params categories must have a name'))
+            raise exceptions.DataError(
+                _("INTERNAL ERROR: params categories must have a name")
+            )
         tabs_cont.addTab(category_name, label=label, container=LabelContainer)
         for param in category.getElementsByTagName("param"):
             widget_kwargs = {}
 
-            param_name = param.getAttribute('name')
-            param_label = param.getAttribute('label')
-            type_ = param.getAttribute('type')
-            if not param_name and type_ != 'text':
-                raise exceptions.DataError(_('INTERNAL ERROR: params must have a name'))
+            param_name = param.getAttribute("name")
+            param_label = param.getAttribute("label")
+            type_ = param.getAttribute("type")
+            if not param_name and type_ != "text":
+                raise exceptions.DataError(_("INTERNAL ERROR: params must have a name"))
 
-            value = param.getAttribute('value') or None
-            callback_id = param.getAttribute('callback_id') or None
+            value = param.getAttribute("value") or None
+            callback_id = param.getAttribute("callback_id") or None
 
-            if type_ == 'list':
+            if type_ == "list":
                 options, selected = _paramsGetListOptions(param)
-                widget_kwargs['options'] = options
-                widget_kwargs['selected'] = selected
-                widget_kwargs['styles'] = ['extensible']
-            elif type_ == 'jids_list':
-                widget_kwargs['jids'] = _paramsGetListJids(param)
+                widget_kwargs["options"] = options
+                widget_kwargs["selected"] = selected
+                widget_kwargs["styles"] = ["extensible"]
+            elif type_ == "jids_list":
+                widget_kwargs["jids"] = _paramsGetListJids(param)
 
             if type_ in ("button", "text"):
                 param_ui.addEmpty()
@@ -378,13 +401,20 @@
                 widget_kwargs["value"] = value
 
             if callback_id:
-                widget_kwargs['callback_id'] = callback_id
-                others = ["%s%s%s" % (category_name, SAT_PARAM_SEPARATOR, other.getAttribute('name'))
-                          for other in category.getElementsByTagName('param')
-                          if other.getAttribute('type') != 'button']
-                widget_kwargs['fields_back'] = others
+                widget_kwargs["callback_id"] = callback_id
+                others = [
+                    "%s%s%s"
+                    % (category_name, SAT_PARAM_SEPARATOR, other.getAttribute("name"))
+                    for other in category.getElementsByTagName("param")
+                    if other.getAttribute("type") != "button"
+                ]
+                widget_kwargs["fields_back"] = others
 
-            widget_kwargs['name'] = "%s%s%s" % (category_name, SAT_PARAM_SEPARATOR, param_name)
+            widget_kwargs["name"] = "%s%s%s" % (
+                category_name,
+                SAT_PARAM_SEPARATOR,
+                param_name,
+            )
 
             param_ui.addWidget(type_, **widget_kwargs)
 
@@ -399,14 +429,21 @@
     @return: a tuple (options, selected_value)
     """
     if len(param.getElementsByTagName("options")) > 0:
-        raise exceptions.DataError(_("The 'options' tag is not allowed in parameter of type 'list'!"))
+        raise exceptions.DataError(
+            _("The 'options' tag is not allowed in parameter of type 'list'!")
+        )
     elems = param.getElementsByTagName("option")
     if len(elems) == 0:
         return []
     options = [elem.getAttribute("value") for elem in elems]
-    selected = [elem.getAttribute("value") for elem in elems if elem.getAttribute("selected") == 'true']
+    selected = [
+        elem.getAttribute("value")
+        for elem in elems
+        if elem.getAttribute("selected") == "true"
+    ]
     return (options, selected)
 
+
 def _paramsGetListJids(param):
     """Retrive jids from a jids_list element.
 
@@ -415,9 +452,11 @@
     @return: a list of jids
     """
     elems = param.getElementsByTagName("jid")
-    jids = [elem.firstChild.data for elem in elems
-            if elem.firstChild is not None
-            and elem.firstChild.nodeType == elem.TEXT_NODE]
+    jids = [
+        elem.firstChild.data
+        for elem in elems
+        if elem.firstChild is not None and elem.firstChild.nodeType == elem.TEXT_NODE
+    ]
     return jids
 
 
@@ -426,6 +465,7 @@
 
 class Element(object):
     """ Base XMLUI element """
+
     type = None
 
     def __init__(self, xmlui, parent=None):
@@ -436,7 +476,7 @@
         """
         assert self.type is not None
         self.children = []
-        if not hasattr(self, 'elem'):
+        if not hasattr(self, "elem"):
             self.elem = parent.xmlui.doc.createElement(self.type)
         self.xmlui = xmlui
         if parent is not None:
@@ -457,7 +497,8 @@
 
 class TopElement(Element):
     """ Main XML Element """
-    type = 'top'
+
+    type = "top"
 
     def __init__(self, xmlui):
         self.elem = xmlui.doc.documentElement
@@ -466,7 +507,8 @@
 
 class TabElement(Element):
     """ Used by TabsContainer to give name and label to tabs."""
-    type = 'tab'
+
+    type = "tab"
 
     def __init__(self, parent, name, label, selected=False):
         """
@@ -479,8 +521,8 @@
         if not isinstance(parent, TabsContainer):
             raise exceptions.DataError(_("TabElement must be a child of TabsContainer"))
         super(TabElement, self).__init__(parent.xmlui, parent)
-        self.elem.setAttribute('name', name)
-        self.elem.setAttribute('label', label)
+        self.elem.setAttribute("name", name)
+        self.elem.setAttribute("label", label)
         if selected:
             self.setSelected(selected)
 
@@ -489,31 +531,34 @@
 
         @param selected (bool): set to True to select this tab
         """
-        self.elem.setAttribute('selected', 'true' if selected else 'false')
+        self.elem.setAttribute("selected", "true" if selected else "false")
 
 
 class FieldBackElement(Element):
     """ Used by ButtonWidget to indicate which field have to be sent back """
-    type = 'field_back'
+
+    type = "field_back"
 
     def __init__(self, parent, name):
         assert isinstance(parent, ButtonWidget)
         super(FieldBackElement, self).__init__(parent.xmlui, parent)
-        self.elem.setAttribute('name', name)
+        self.elem.setAttribute("name", name)
 
 
 class InternalFieldElement(Element):
     """ Used by internal callbacks to indicate which fields are manipulated """
-    type = 'internal_field'
+
+    type = "internal_field"
 
     def __init__(self, parent, name):
         super(InternalFieldElement, self).__init__(parent.xmlui, parent)
-        self.elem.setAttribute('name', name)
+        self.elem.setAttribute("name", name)
 
 
 class InternalDataElement(Element):
     """ Used by internal callbacks to retrieve extra data """
-    type = 'internal_data'
+
+    type = "internal_data"
 
     def __init__(self, parent, children):
         super(InternalDataElement, self).__init__(parent.xmlui, parent)
@@ -524,7 +569,8 @@
 
 class OptionElement(Element):
     """" Used by ListWidget to specify options """
-    type = 'option'
+
+    type = "option"
 
     def __init__(self, parent, option, selected=False):
         """
@@ -541,15 +587,16 @@
             value, label = option
         else:
             raise NotImplementedError
-        self.elem.setAttribute('value', value)
-        self.elem.setAttribute('label', label)
+        self.elem.setAttribute("value", value)
+        self.elem.setAttribute("label", label)
         if selected:
-            self.elem.setAttribute('selected', 'true')
+            self.elem.setAttribute("selected", "true")
 
 
 class JidElement(Element):
     """" Used by JidsListWidget to specify jids"""
-    type = 'jid'
+
+    type = "jid"
 
     def __init__(self, parent, jid_):
         """
@@ -569,7 +616,8 @@
 
 class RowElement(Element):
     """" Used by AdvancedListContainer """
-    type = 'row'
+
+    type = "row"
 
     def __init__(self, parent):
         assert isinstance(parent, AdvancedListContainer)
@@ -577,13 +625,14 @@
         if parent.next_row_idx is not None:
             if parent.auto_index:
                 raise exceptions.DataError(_("Can't set row index if auto_index is True"))
-            self.elem.setAttribute('index', parent.next_row_idx)
+            self.elem.setAttribute("index", parent.next_row_idx)
             parent.next_row_idx = None
 
 
 class HeaderElement(Element):
     """" Used by AdvancedListContainer """
-    type = 'header'
+
+    type = "header"
 
     def __init__(self, parent, name=None, label=None, description=None):
         """
@@ -595,11 +644,11 @@
         assert isinstance(parent, AdvancedListContainer)
         super(HeaderElement, self).__init__(parent.xmlui, parent)
         if name:
-            self.elem.setAttribute('name', name)
+            self.elem.setAttribute("name", name)
         if label:
-            self.elem.setAttribute('label', label)
+            self.elem.setAttribute("label", label)
         if description:
-            self.elem.setAttribute('description', description)
+            self.elem.setAttribute("description", description)
 
 
 ## Containers ##
@@ -607,6 +656,7 @@
 
 class Container(Element):
     """ And Element which contains other ones and has a layout """
+
     type = None
 
     def __init__(self, xmlui, parent=None):
@@ -615,9 +665,9 @@
         @param xmlui: XMLUI instance
         @parent: parent element or None
         """
-        self.elem = xmlui.doc.createElement('container')
+        self.elem = xmlui.doc.createElement("container")
         super(Container, self).__init__(xmlui, parent)
-        self.elem.setAttribute('type', self.type)
+        self.elem.setAttribute("type", self.type)
 
     def getParentContainer(self):
         """ Return first parent container
@@ -625,8 +675,7 @@
         @return: parent container or None
         """
         current = self.parent
-        while(not isinstance(current, (Container)) and
-              current is not None):
+        while not isinstance(current, (Container)) and current is not None:
             current = current.parent
         return current
 
@@ -676,9 +725,21 @@
 
 class AdvancedListContainer(Container):
     """A list which can contain other widgets, headers, etc"""
+
     type = "advanced_list"
 
-    def __init__(self, xmlui, callback_id=None, name=None, headers=None, items=None, columns=None, selectable='no', auto_index=False, parent=None):
+    def __init__(
+        self,
+        xmlui,
+        callback_id=None,
+        name=None,
+        headers=None,
+        items=None,
+        columns=None,
+        selectable="no",
+        auto_index=False,
+        parent=None,
+    ):
         """Create an advanced list
 
         @param headers: optional headers information
@@ -691,7 +752,7 @@
         @param auto_index: if True, indexes will be generated by frontends, starting from 0
         @return: created element
         """
-        assert selectable in ('no', 'single')
+        assert selectable in ("no", "single")
         if not items and columns is None:
             raise exceptions.DataError(_("either items or columns need do be filled"))
         if headers is None:
@@ -706,17 +767,19 @@
         self.current_row = None
         if headers:
             if len(headers) != self._columns:
-                raise exceptions.DataError(_("Headers lenght doesn't correspond to columns"))
+                raise exceptions.DataError(
+                    _("Headers lenght doesn't correspond to columns")
+                )
             self.addHeaders(headers)
         if items:
             self.addItems(items)
-        self.elem.setAttribute('columns', str(self._columns))
+        self.elem.setAttribute("columns", str(self._columns))
         if callback_id is not None:
-            self.elem.setAttribute('callback', callback_id)
-        self.elem.setAttribute('selectable', selectable)
+            self.elem.setAttribute("callback", callback_id)
+        self.elem.setAttribute("selectable", selectable)
         self.auto_index = auto_index
         if auto_index:
-            self.elem.setAttribute('auto_index', 'true')
+            self.elem.setAttribute("auto_index", "true")
         self.next_row_idx = None
 
     def addHeaders(self, headers):
@@ -770,14 +833,18 @@
         @param name: name of the element or None
         @param parent: parent element or None
         """
-        self.elem = xmlui.doc.createElement('widget')
+        self.elem = xmlui.doc.createElement("widget")
         super(Widget, self).__init__(xmlui, parent)
         if name:
-            self.elem.setAttribute('name', name)
+            self.elem.setAttribute("name", name)
             if name in xmlui.named_widgets:
-                raise exceptions.ConflictError(_(u'A widget with the name "{name}" already exists.').format(name=name))
+                raise exceptions.ConflictError(
+                    _(u'A widget with the name "{name}" already exists.').format(
+                        name=name
+                    )
+                )
             xmlui.named_widgets[name] = self
-        self.elem.setAttribute('type', self.type)
+        self.elem.setAttribute("type", self.type)
 
     def setInternalCallback(self, callback, fields, data_elts=None):
         """Set an internal UI callback when the widget value is changed.
@@ -797,7 +864,7 @@
         @param fields (list): a list of widget names (string)
         @param data_elts (list[Element]): extra data elements
         """
-        self.elem.setAttribute('internal_callback', callback)
+        self.elem.setAttribute("internal_callback", callback)
         if fields:
             for field in fields:
                 InternalFieldElement(self, field)
@@ -807,16 +874,18 @@
 
 class EmptyWidget(Widget):
     """Place holder widget"""
-    type = 'empty'
+
+    type = "empty"
 
 
 class TextWidget(Widget):
     """Used for blob of text"""
-    type = 'text'
+
+    type = "text"
 
     def __init__(self, xmlui, value, name=None, parent=None):
         super(TextWidget, self).__init__(xmlui, name, parent)
-        value_elt = self.xmlui.doc.createElement('value')
+        value_elt = self.xmlui.doc.createElement("value")
         text = self.xmlui.doc.createTextNode(value)
         value_elt.appendChild(text)
         self.elem.appendChild(value_elt)
@@ -831,29 +900,31 @@
 
     used most of time to display the desciption or name of the next widget
     """
-    type = 'label'
+
+    type = "label"
 
     def __init__(self, xmlui, label, name=None, parent=None):
         super(LabelWidget, self).__init__(xmlui, name, parent)
-        self.elem.setAttribute('value', label)
+        self.elem.setAttribute("value", label)
 
 
 class JidWidget(Widget):
     """Used to display a Jabber ID, some specific methods can be added"""
-    type = 'jid'
+
+    type = "jid"
 
     def __init__(self, xmlui, jid, name=None, parent=None):
         super(JidWidget, self).__init__(xmlui, name, parent)
         try:
-            self.elem.setAttribute('value', jid.full())
+            self.elem.setAttribute("value", jid.full())
         except AttributeError:
-            self.elem.setAttribute('value', unicode(jid))
+            self.elem.setAttribute("value", unicode(jid))
 
 
 class DividerWidget(Widget):
-    type = 'divider'
+    type = "divider"
 
-    def __init__(self, xmlui, style='line', name=None, parent=None):
+    def __init__(self, xmlui, style="line", name=None, parent=None):
         """ Create a divider
 
         @param xmlui: XMLUI instance
@@ -868,7 +939,7 @@
 
         """
         super(DividerWidget, self).__init__(xmlui, name, parent)
-        self.elem.setAttribute('style', style)
+        self.elem.setAttribute("style", style)
 
 
 ### Inputs ###
@@ -879,19 +950,20 @@
 
     used mainly in forms
     """
+
     def __init__(self, xmlui, name=None, parent=None, read_only=False):
         super(InputWidget, self).__init__(xmlui, name, parent)
         if read_only:
-            self.elem.setAttribute('read_only', 'true')
+            self.elem.setAttribute("read_only", "true")
 
 
 class StringWidget(InputWidget):
-    type = 'string'
+    type = "string"
 
     def __init__(self, xmlui, value=None, name=None, parent=None, read_only=False):
         super(StringWidget, self).__init__(xmlui, name, parent, read_only=read_only)
         if value:
-            value_elt = self.xmlui.doc.createElement('value')
+            value_elt = self.xmlui.doc.createElement("value")
             text = self.xmlui.doc.createTextNode(value)
             value_elt.appendChild(text)
             self.elem.appendChild(value_elt)
@@ -902,20 +974,20 @@
 
 
 class PasswordWidget(StringWidget):
-    type = 'password'
+    type = "password"
 
 
 class TextBoxWidget(StringWidget):
-    type = 'textbox'
+    type = "textbox"
 
 
 class JidInputWidget(StringWidget):
-    type = 'jid_input'
+    type = "jid_input"
 
 
 # TODO handle min and max values
 class IntWidget(StringWidget):
-    type = 'int'
+    type = "int"
 
     def __init__(self, xmlui, value=0, name=None, parent=None, read_only=False):
         try:
@@ -926,25 +998,27 @@
 
 
 class BoolWidget(InputWidget):
-    type = 'bool'
+    type = "bool"
 
-    def __init__(self, xmlui, value='false', name=None, parent=None, read_only=False):
+    def __init__(self, xmlui, value="false", name=None, parent=None, read_only=False):
         if isinstance(value, bool):
-            value = 'true' if value else 'false'
-        elif value == '0':
-            value = 'false'
-        elif value == '1':
-            value = 'true'
-        if value not in ('true', 'false'):
+            value = "true" if value else "false"
+        elif value == "0":
+            value = "false"
+        elif value == "1":
+            value = "true"
+        if value not in ("true", "false"):
             raise exceptions.DataError(_("Value must be 0, 1, false or true"))
         super(BoolWidget, self).__init__(xmlui, name, parent, read_only=read_only)
-        self.elem.setAttribute('value', value)
+        self.elem.setAttribute("value", value)
 
 
 class ButtonWidget(Widget):
-    type = 'button'
+    type = "button"
 
-    def __init__(self, xmlui, callback_id, value=None, fields_back=None, name=None, parent=None):
+    def __init__(
+        self, xmlui, callback_id, value=None, fields_back=None, name=None, parent=None
+    ):
         """Add a button
 
         @param callback_id: callback which will be called if button is pressed
@@ -956,18 +1030,20 @@
         if fields_back is None:
             fields_back = []
         super(ButtonWidget, self).__init__(xmlui, name, parent)
-        self.elem.setAttribute('callback', callback_id)
+        self.elem.setAttribute("callback", callback_id)
         if value:
-            self.elem.setAttribute('value', value)
+            self.elem.setAttribute("value", value)
         for field in fields_back:
             FieldBackElement(self, field)
 
 
 class ListWidget(InputWidget):
-    type = 'list'
-    STYLES = (u'multi', u'noselect', u'extensible', u'reducible', u'inline')
+    type = "list"
+    STYLES = (u"multi", u"noselect", u"extensible", u"reducible", u"inline")
 
-    def __init__(self, xmlui, options, selected=None, styles=None, name=None, parent=None):
+    def __init__(
+        self, xmlui, options, selected=None, styles=None, name=None, parent=None
+    ):
         """
 
         @param xmlui
@@ -991,8 +1067,12 @@
             styles = set()
         else:
             styles = set(styles)
-        if u'noselect' in styles and (u'multi' in styles or selected):
-                raise exceptions.DataError(_(u'"multi" flag and "selected" option are not compatible with "noselect" flag'))
+        if u"noselect" in styles and (u"multi" in styles or selected):
+            raise exceptions.DataError(
+                _(
+                    u'"multi" flag and "selected" option are not compatible with "noselect" flag'
+                )
+            )
         if not options:
             # we can have no options if we get a submitted data form
             # but we can't use submitted values directly,
@@ -1018,7 +1098,7 @@
         if not styles.issubset(self.STYLES):
             raise exceptions.DataError(_(u"invalid styles"))
         for style in styles:
-            self.elem.setAttribute(style, 'yes')
+            self.elem.setAttribute(style, "yes")
         # TODO: check flags incompatibily (noselect and multi) like in __init__
 
     def setStyle(self, style):
@@ -1028,13 +1108,15 @@
     def value(self):
         """Return the value of first selected option"""
         for child in self.elem.childNodes:
-            if child.tagName == u'option' and child.getAttribute(u'selected') == u'true':
-                return child.getAttribute(u'value')
-        return u''
+            if child.tagName == u"option" and child.getAttribute(u"selected") == u"true":
+                return child.getAttribute(u"value")
+        return u""
+
 
 class JidsListWidget(InputWidget):
     """A list of text or jids where elements can be added/removed or modified"""
-    type = 'jids_list'
+
+    type = "jids_list"
 
     def __init__(self, xmlui, jids, styles=None, name=None, parent=None):
         """
@@ -1047,12 +1129,12 @@
         """
         super(JidsListWidget, self).__init__(xmlui, name, parent)
         styles = set() if styles is None else set(styles)
-        if not styles.issubset([]): # TODO
+        if not styles.issubset([]):  # TODO
             raise exceptions.DataError(_("invalid styles"))
         for style in styles:
-            self.elem.setAttribute(style, 'yes')
+            self.elem.setAttribute(style, "yes")
         if not jids:
-            log.debug('empty jids list')
+            log.debug("empty jids list")
         else:
             self.addJids(jids)
 
@@ -1066,11 +1148,14 @@
 
 class DialogElement(Element):
     """Main dialog element """
-    type = 'dialog'
+
+    type = "dialog"
 
     def __init__(self, parent, type_, level=None):
         if not isinstance(parent, TopElement):
-            raise exceptions.DataError(_("DialogElement must be a direct child of TopElement"))
+            raise exceptions.DataError(
+                _("DialogElement must be a direct child of TopElement")
+            )
         super(DialogElement, self).__init__(parent.xmlui, parent)
         self.elem.setAttribute(C.XMLUI_DATA_TYPE, type_)
         self.elem.setAttribute(C.XMLUI_DATA_LVL, level or C.XMLUI_DATA_LVL_DEFAULT)
@@ -1078,11 +1163,14 @@
 
 class MessageElement(Element):
     """Element with the instruction message"""
+
     type = C.XMLUI_DATA_MESS
 
     def __init__(self, parent, message):
         if not isinstance(parent, DialogElement):
-            raise exceptions.DataError(_("MessageElement must be a direct child of DialogElement"))
+            raise exceptions.DataError(
+                _("MessageElement must be a direct child of DialogElement")
+            )
         super(MessageElement, self).__init__(parent.xmlui, parent)
         message_txt = self.xmlui.doc.createTextNode(message)
         self.elem.appendChild(message_txt)
@@ -1090,24 +1178,30 @@
 
 class ButtonsElement(Element):
     """Buttons element which indicate which set to use"""
-    type = 'buttons'
+
+    type = "buttons"
 
     def __init__(self, parent, set_):
         if not isinstance(parent, DialogElement):
-            raise exceptions.DataError(_("ButtonsElement must be a direct child of DialogElement"))
+            raise exceptions.DataError(
+                _("ButtonsElement must be a direct child of DialogElement")
+            )
         super(ButtonsElement, self).__init__(parent.xmlui, parent)
-        self.elem.setAttribute('set', set_)
+        self.elem.setAttribute("set", set_)
 
 
 class FileElement(Element):
     """File element used for FileDialog"""
-    type = 'file'
+
+    type = "file"
 
     def __init__(self, parent, type_):
         if not isinstance(parent, DialogElement):
-            raise exceptions.DataError(_("FileElement must be a direct child of DialogElement"))
+            raise exceptions.DataError(
+                _("FileElement must be a direct child of DialogElement")
+            )
         super(FileElement, self).__init__(parent.xmlui, parent)
-        self.elem.setAttribute('type', type_)
+        self.elem.setAttribute("type", type_)
 
 
 ## XMLUI main class
@@ -1116,7 +1210,15 @@
 class XMLUI(object):
     """This class is used to create a user interface (form/window/parameters/etc) using SàT XML"""
 
-    def __init__(self, panel_type="window", container="vertical", dialog_opt=None, title=None, submit_id=None, session_id=None):
+    def __init__(
+        self,
+        panel_type="window",
+        container="vertical",
+        dialog_opt=None,
+        title=None,
+        submit_id=None,
+        session_id=None,
+    ):
         """Init SàT XML Panel
 
         @param panel_type: one of
@@ -1162,15 +1264,23 @@
         @param session_id: use to keep a session attached to the dialog, must be returned by frontends
         @attribute named_widgets(dict): map from name to widget
         """
-        self._introspect() # FIXME: why doing that on each XMLUI ? should be done once
-        if panel_type not in [C.XMLUI_WINDOW, C.XMLUI_FORM, C.XMLUI_PARAM, C.XMLUI_POPUP, C.XMLUI_DIALOG]:
+        self._introspect()  # FIXME: why doing that on each XMLUI ? should be done once
+        if panel_type not in [
+            C.XMLUI_WINDOW,
+            C.XMLUI_FORM,
+            C.XMLUI_PARAM,
+            C.XMLUI_POPUP,
+            C.XMLUI_DIALOG,
+        ]:
             raise exceptions.DataError(_("Unknown panel type [%s]") % panel_type)
         if panel_type == C.XMLUI_FORM and submit_id is None:
             raise exceptions.DataError(_("form XMLUI need a submit_id"))
         if not isinstance(container, basestring):
             raise exceptions.DataError(_("container argument must be a string"))
         if dialog_opt is not None and panel_type != C.XMLUI_DIALOG:
-            raise exceptions.DataError(_("dialog_opt can only be used with dialog panels"))
+            raise exceptions.DataError(
+                _("dialog_opt can only be used with dialog panels")
+            )
         self.type = panel_type
         impl = minidom.getDOMImplementation()
 
@@ -1197,11 +1307,11 @@
         for obj in globals().values():
             try:
                 if issubclass(obj, Widget):
-                    if obj.__name__ == 'Widget':
+                    if obj.__name__ == "Widget":
                         continue
                     self._widgets[obj.type] = obj
                 elif issubclass(obj, Container):
-                    if obj.__name__ == 'Container':
+                    if obj.__name__ == "Container":
                         continue
                     self._containers[obj.type] = obj
             except TypeError:
@@ -1211,20 +1321,26 @@
         self.doc.unlink()
 
     def __getattr__(self, name):
-        if name.startswith("add") and name not in ('addWidget',):  # addWidgetName(...) create an instance of WidgetName
+        if name.startswith("add") and name not in (
+            "addWidget",
+        ):  # addWidgetName(...) create an instance of WidgetName
             if self.type == C.XMLUI_DIALOG:
                 raise exceptions.InternalError(_("addXXX can't be used with dialogs"))
             class_name = name[3:] + "Widget"
             if class_name in globals():
                 cls = globals()[class_name]
                 if issubclass(cls, Widget):
+
                     def createWidget(*args, **kwargs):
                         if "parent" not in kwargs:
                             kwargs["parent"] = self.current_container
-                        if "name" not in kwargs and issubclass(cls, InputWidget):  # name can be given as first argument or in keyword arguments for InputWidgets
+                        if "name" not in kwargs and issubclass(
+                            cls, InputWidget
+                        ):  # name can be given as first argument or in keyword arguments for InputWidgets
                             args = list(args)
                             kwargs["name"] = args.pop(0)
                         return cls(self, *args, **kwargs)
+
                     return createWidget
         return object.__getattribute__(self, name)
 
@@ -1270,8 +1386,13 @@
 
     def _createDialog(self, dialog_opt):
         dialog_type = dialog_opt.setdefault(C.XMLUI_DATA_TYPE, C.XMLUI_DIALOG_MESSAGE)
-        if dialog_type in [C.XMLUI_DIALOG_CONFIRM, C.XMLUI_DIALOG_FILE] and self.submit_id is None:
-            raise exceptions.InternalError(_("Submit ID must be filled for this kind of dialog"))
+        if (
+            dialog_type in [C.XMLUI_DIALOG_CONFIRM, C.XMLUI_DIALOG_FILE]
+            and self.submit_id is None
+        ):
+            raise exceptions.InternalError(
+                _("Submit ID must be filled for this kind of dialog")
+            )
         top_element = TopElement(self)
         level = dialog_opt.get(C.XMLUI_DATA_LVL)
         dialog_elt = DialogElement(top_element, dialog_type, level)
@@ -1309,9 +1430,15 @@
         @param container: either container type (container it then created),
                           or an Container instance"""
         if isinstance(container, basestring):
-            self.current_container = self._createContainer(container, self.current_container.getParentContainer() or self.main_container, **kwargs)
+            self.current_container = self._createContainer(
+                container,
+                self.current_container.getParentContainer() or self.main_container,
+                **kwargs
+            )
         else:
-            self.current_container = self.main_container if container is None else container
+            self.current_container = (
+                self.main_container if container is None else container
+            )
         assert isinstance(self.current_container, Container)
         return self.current_container
 
@@ -1331,7 +1458,8 @@
 
 # Some sugar for XMLUI dialogs
 
-def note(message, title='', level=C.XMLUI_DATA_LVL_INFO):
+
+def note(message, title="", level=C.XMLUI_DATA_LVL_INFO):
     """sugar to easily create a Note Dialog
 
     @param message(unicode): body of the note
@@ -1339,19 +1467,22 @@
     @param level(unicode): one of C.XMLUI_DATA_LVL_*
     @return(XMLUI): instance of XMLUI
     """
-    note_xmlui = XMLUI(C.XMLUI_DIALOG, dialog_opt={
-                       C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_NOTE,
-                       C.XMLUI_DATA_MESS: message,
-                       C.XMLUI_DATA_LVL: level},
-                       title=title
-                       )
+    note_xmlui = XMLUI(
+        C.XMLUI_DIALOG,
+        dialog_opt={
+            C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_NOTE,
+            C.XMLUI_DATA_MESS: message,
+            C.XMLUI_DATA_LVL: level,
+        },
+        title=title,
+    )
     return note_xmlui
 
 
-def quickNote(host, client, message, title='', level=C.XMLUI_DATA_LVL_INFO):
+def quickNote(host, client, message, title="", level=C.XMLUI_DATA_LVL_INFO):
     """more sugar to do the whole note process"""
     note_ui = note(message, title, level)
-    host.actionNew({'xmlui': note_ui.toXml()}, profile=client.profile)
+    host.actionNew({"xmlui": note_ui.toXml()}, profile=client.profile)
 
 
 def deferredUI(host, xmlui, chained=False):
@@ -1363,7 +1494,7 @@
         useful when backend is in a series of dialogs with an ui
     @return (D(data)): a deferred which fire the data
     """
-    assert xmlui.submit_id == ''
+    assert xmlui.submit_id == ""
     xmlui_d = defer.Deferred()
 
     def onSubmit(data, profile):
@@ -1373,7 +1504,15 @@
     xmlui.submit_id = host.registerCallback(onSubmit, with_data=True, one_shot=True)
     return xmlui_d
 
-def deferXMLUI(host, xmlui, action_extra=None, security_limit=C.NO_SECURITY_LIMIT, chained=False, profile=C.PROF_KEY_NONE):
+
+def deferXMLUI(
+    host,
+    xmlui,
+    action_extra=None,
+    security_limit=C.NO_SECURITY_LIMIT,
+    chained=False,
+    profile=C.PROF_KEY_NONE,
+):
     """Create a deferred linked to XMLUI
 
     @param xmlui(XMLUI): instance of the XMLUI
@@ -1387,14 +1526,29 @@
     @return (data): a deferred which fire the data
     """
     xmlui_d = deferredUI(host, xmlui, chained)
-    action_data = {'xmlui': xmlui.toXml()}
+    action_data = {"xmlui": xmlui.toXml()}
     if action_extra is not None:
         action_data.update(action_extra)
-    host.actionNew(action_data, security_limit=security_limit, keep_id=xmlui.submit_id, profile=profile)
+    host.actionNew(
+        action_data,
+        security_limit=security_limit,
+        keep_id=xmlui.submit_id,
+        profile=profile,
+    )
     return xmlui_d
 
-def deferDialog(host, message, title=u'Please confirm', type_=C.XMLUI_DIALOG_CONFIRM, options=None,
-        action_extra=None, security_limit=C.NO_SECURITY_LIMIT, chained=False, profile=C.PROF_KEY_NONE):
+
+def deferDialog(
+    host,
+    message,
+    title=u"Please confirm",
+    type_=C.XMLUI_DIALOG_CONFIRM,
+    options=None,
+    action_extra=None,
+    security_limit=C.NO_SECURITY_LIMIT,
+    chained=False,
+    profile=C.PROF_KEY_NONE,
+):
     """Create a submitable dialog and manage it with a deferred
 
     @param message(unicode): message to display
@@ -1410,22 +1564,26 @@
     @return (dict): Deferred dict
     """
     assert profile is not None
-    dialog_opt = {'type': type_, 'message': message}
+    dialog_opt = {"type": type_, "message": message}
     if options is not None:
         dialog_opt.update(options)
-    dialog = XMLUI(C.XMLUI_DIALOG, title=title, dialog_opt=dialog_opt, submit_id='')
+    dialog = XMLUI(C.XMLUI_DIALOG, title=title, dialog_opt=dialog_opt, submit_id="")
     return deferXMLUI(host, dialog, action_extra, security_limit, chained, profile)
 
+
 def deferConfirm(*args, **kwargs):
     """call deferDialog and return a boolean instead of the whole data dict"""
     d = deferDialog(*args, **kwargs)
-    d.addCallback(lambda data: C.bool(data['answer']))
+    d.addCallback(lambda data: C.bool(data["answer"]))
     return d
 
+
 # Misc other funtions
 
+
 class ElementParser(object):
     """callable class to parse XML string into Element"""
+
     # XXX: Found at http://stackoverflow.com/questions/2093400/how-to-create-twisted-words-xish-domish-element-entirely-from-raw-xml/2095942#2095942
 
     def _escapeHTML(self, matchobj):
@@ -1438,7 +1596,7 @@
                 return unichr(htmlentitydefs.name2codepoint[entity])
             except KeyError:
                 log.warning(u"removing unknown entity {}".format(entity))
-                return u''
+                return u""
 
     def __call__(self, raw_xml, force_spaces=False, namespace=None):
         """
@@ -1473,9 +1631,9 @@
         parser.DocumentEndEvent = onEnd
         tmp = domish.Element((None, "s"))
         if force_spaces:
-            raw_xml = raw_xml.replace('\n', ' ').replace('\t', ' ')
+            raw_xml = raw_xml.replace("\n", " ").replace("\t", " ")
         tmp.addRawXml(raw_xml)
-        parser.parse(tmp.toXml().encode('utf-8'))
+        parser.parse(tmp.toXml().encode("utf-8"))
         top_elt = self.result.firstChildElement()
         # we now can check if there was a unique element on the top
         # and remove our wrapping <div/> is this was the case
@@ -1497,7 +1655,8 @@
             data.append(child.wholeText)
     return u"".join(data)
 
-def findAll(elt, namespaces=None, names=None, ):
+
+def findAll(elt, namespaces=None, names=None):
     """Find child element at any depth matching criteria
 
     @param elt(domish.Element): top parent of the elements to find
@@ -1508,14 +1667,16 @@
     @return ((G)domish.Element): found elements
     """
     if isinstance(namespaces, basestring):
-        namespaces=tuple((namespaces,))
+        namespaces = tuple((namespaces,))
     if isinstance(names, basestring):
-        names=tuple((names,))
+        names = tuple((names,))
 
     for child in elt.elements():
-        if (domish.IElement.providedBy(child) and
-            (not names or child.name in names) and
-            (not namespaces or child.uri in namespaces)):
+        if (
+            domish.IElement.providedBy(child)
+            and (not names or child.name in names)
+            and (not namespaces or child.uri in namespaces)
+        ):
             yield child
         for found in findAll(child, namespaces, names):
             yield found
--- a/sat_frontends/bridge/bridge_frontend.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/bridge/bridge_frontend.py	Wed Jun 27 20:14:46 2018 +0200
@@ -1,5 +1,5 @@
 #!/usr/bin/env python2
-#-*- coding: utf-8 -*-
+# -*- coding: utf-8 -*-
 
 # SAT communication bridge
 # Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org)
@@ -21,7 +21,7 @@
 class BridgeException(Exception):
     """An exception which has been raised from the backend and arrived to the frontend."""
 
-    def __init__(self, name, message='', condition=''):
+    def __init__(self, name, message="", condition=""):
         """
 
         @param name (str): full exception class name (with module)
@@ -31,11 +31,11 @@
         Exception.__init__(self)
         self.fullname = unicode(name)
         self.message = unicode(message)
-        self.condition = unicode(condition) if condition else ''
-        self.module, dummy, self.classname = unicode(self.fullname).rpartition('.')
+        self.condition = unicode(condition) if condition else ""
+        self.module, dummy, self.classname = unicode(self.fullname).rpartition(".")
 
     def __str__(self):
-        message = (': %s' % self.message) if self.message else ''
+        message = (": %s" % self.message) if self.message else ""
         return self.classname + message
 
     def __eq__(self, other):
--- a/sat_frontends/bridge/pb.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/bridge/pb.py	Wed Jun 27 20:14:46 2018 +0200
@@ -1,5 +1,5 @@
 #!/usr/bin/env python2
-#-*- coding: utf-8 -*-
+# -*- coding: utf-8 -*-
 
 # SAT communication bridge
 # Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org)
@@ -18,6 +18,7 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from sat.core import exceptions
 from twisted.spread import pb
@@ -25,11 +26,9 @@
 
 
 class SignalsHandler(pb.Referenceable):
-
     def __getattr__(self, name):
         if name.startswith("remote_"):
-            log.debug(u"calling an unregistered signal: {name}".format(
-                name = name[7:]))
+            log.debug(u"calling an unregistered signal: {name}".format(name=name[7:]))
             return lambda *args, **kwargs: None
 
         else:
@@ -43,13 +42,15 @@
         except AttributeError:
             pass
         else:
-            raise exceptions.InternalError(u"{name} signal handler has been registered twice".format(
-                name = method_name))
+            raise exceptions.InternalError(
+                u"{name} signal handler has been registered twice".format(
+                    name=method_name
+                )
+            )
         setattr(self, method_name, handler)
 
 
 class Bridge(object):
-
     def __init__(self):
         self.signals_handler = SignalsHandler()
 
@@ -81,11 +82,11 @@
         callback = errback = None
         if kwargs:
             try:
-                callback = kwargs.pop('callback')
+                callback = kwargs.pop("callback")
             except KeyError:
                 pass
             try:
-                errback = kwargs.pop('errback')
+                errback = kwargs.pop("errback")
             except KeyError:
                 pass
         elif len(args) >= 2 and callable(args[-1]) and callable(args[-2]):
@@ -132,7 +133,9 @@
             errback = self._generic_errback
         d.addErrback(errback)
 
-    def addContact(self, entity_jid, profile_key="@DEFAULT@", callback=None, errback=None):
+    def addContact(
+        self, entity_jid, profile_key="@DEFAULT@", callback=None, errback=None
+    ):
         d = self.root.callRemote("addContact", entity_jid, profile_key)
         if callback is not None:
             d.addCallback(lambda dummy: callback())
@@ -148,23 +151,50 @@
             errback = self._generic_errback
         d.addErrback(errback)
 
-    def asyncGetParamA(self, name, category, attribute="value", security_limit=-1, profile_key="@DEFAULT@", callback=None, errback=None):
-        d = self.root.callRemote("asyncGetParamA", name, category, attribute, security_limit, profile_key)
+    def asyncGetParamA(
+        self,
+        name,
+        category,
+        attribute="value",
+        security_limit=-1,
+        profile_key="@DEFAULT@",
+        callback=None,
+        errback=None,
+    ):
+        d = self.root.callRemote(
+            "asyncGetParamA", name, category, attribute, security_limit, profile_key
+        )
         if callback is not None:
             d.addCallback(callback)
         if errback is None:
             errback = self._generic_errback
         d.addErrback(errback)
 
-    def asyncGetParamsValuesFromCategory(self, category, security_limit=-1, profile_key="@DEFAULT@", callback=None, errback=None):
-        d = self.root.callRemote("asyncGetParamsValuesFromCategory", category, security_limit, profile_key)
+    def asyncGetParamsValuesFromCategory(
+        self,
+        category,
+        security_limit=-1,
+        profile_key="@DEFAULT@",
+        callback=None,
+        errback=None,
+    ):
+        d = self.root.callRemote(
+            "asyncGetParamsValuesFromCategory", category, security_limit, profile_key
+        )
         if callback is not None:
             d.addCallback(callback)
         if errback is None:
             errback = self._generic_errback
         d.addErrback(errback)
 
-    def connect(self, profile_key="@DEFAULT@", password='', options={}, callback=None, errback=None):
+    def connect(
+        self,
+        profile_key="@DEFAULT@",
+        password="",
+        options={},
+        callback=None,
+        errback=None,
+    ):
         d = self.root.callRemote("connect", profile_key, password, options)
         if callback is not None:
             d.addCallback(callback)
@@ -172,7 +202,9 @@
             errback = self._generic_errback
         d.addErrback(errback)
 
-    def delContact(self, entity_jid, profile_key="@DEFAULT@", callback=None, errback=None):
+    def delContact(
+        self, entity_jid, profile_key="@DEFAULT@", callback=None, errback=None
+    ):
         d = self.root.callRemote("delContact", entity_jid, profile_key)
         if callback is not None:
             d.addCallback(lambda dummy: callback())
@@ -180,15 +212,45 @@
             errback = self._generic_errback
         d.addErrback(errback)
 
-    def discoFindByFeatures(self, namespaces, identities, bare_jid=False, service=True, roster=True, own_jid=True, local_device=False, profile_key=u"@DEFAULT@", callback=None, errback=None):
-        d = self.root.callRemote("discoFindByFeatures", namespaces, identities, bare_jid, service, roster, own_jid, local_device, profile_key)
+    def discoFindByFeatures(
+        self,
+        namespaces,
+        identities,
+        bare_jid=False,
+        service=True,
+        roster=True,
+        own_jid=True,
+        local_device=False,
+        profile_key=u"@DEFAULT@",
+        callback=None,
+        errback=None,
+    ):
+        d = self.root.callRemote(
+            "discoFindByFeatures",
+            namespaces,
+            identities,
+            bare_jid,
+            service,
+            roster,
+            own_jid,
+            local_device,
+            profile_key,
+        )
         if callback is not None:
             d.addCallback(callback)
         if errback is None:
             errback = self._generic_errback
         d.addErrback(errback)
 
-    def discoInfos(self, entity_jid, node=u'', use_cache=True, profile_key=u"@DEFAULT@", callback=None, errback=None):
+    def discoInfos(
+        self,
+        entity_jid,
+        node=u"",
+        use_cache=True,
+        profile_key=u"@DEFAULT@",
+        callback=None,
+        errback=None,
+    ):
         d = self.root.callRemote("discoInfos", entity_jid, node, use_cache, profile_key)
         if callback is not None:
             d.addCallback(callback)
@@ -196,7 +258,15 @@
             errback = self._generic_errback
         d.addErrback(errback)
 
-    def discoItems(self, entity_jid, node=u'', use_cache=True, profile_key=u"@DEFAULT@", callback=None, errback=None):
+    def discoItems(
+        self,
+        entity_jid,
+        node=u"",
+        use_cache=True,
+        profile_key=u"@DEFAULT@",
+        callback=None,
+        errback=None,
+    ):
         d = self.root.callRemote("discoItems", entity_jid, node, use_cache, profile_key)
         if callback is not None:
             d.addCallback(callback)
@@ -228,7 +298,9 @@
             errback = self._generic_errback
         d.addErrback(errback)
 
-    def getContactsFromGroup(self, group, profile_key="@DEFAULT@", callback=None, errback=None):
+    def getContactsFromGroup(
+        self, group, profile_key="@DEFAULT@", callback=None, errback=None
+    ):
         d = self.root.callRemote("getContactsFromGroup", group, profile_key)
         if callback is not None:
             d.addCallback(callback)
@@ -260,7 +332,9 @@
             errback = self._generic_errback
         d.addErrback(errback)
 
-    def getMainResource(self, contact_jid, profile_key="@DEFAULT@", callback=None, errback=None):
+    def getMainResource(
+        self, contact_jid, profile_key="@DEFAULT@", callback=None, errback=None
+    ):
         d = self.root.callRemote("getMainResource", contact_jid, profile_key)
         if callback is not None:
             d.addCallback(callback)
@@ -268,7 +342,15 @@
             errback = self._generic_errback
         d.addErrback(errback)
 
-    def getParamA(self, name, category, attribute="value", profile_key="@DEFAULT@", callback=None, errback=None):
+    def getParamA(
+        self,
+        name,
+        category,
+        attribute="value",
+        profile_key="@DEFAULT@",
+        callback=None,
+        errback=None,
+    ):
         d = self.root.callRemote("getParamA", name, category, attribute, profile_key)
         if callback is not None:
             d.addCallback(callback)
@@ -284,7 +366,14 @@
             errback = self._generic_errback
         d.addErrback(errback)
 
-    def getParamsUI(self, security_limit=-1, app='', profile_key="@DEFAULT@", callback=None, errback=None):
+    def getParamsUI(
+        self,
+        security_limit=-1,
+        app="",
+        profile_key="@DEFAULT@",
+        callback=None,
+        errback=None,
+    ):
         d = self.root.callRemote("getParamsUI", security_limit, app, profile_key)
         if callback is not None:
             d.addCallback(callback)
@@ -324,8 +413,20 @@
             errback = self._generic_errback
         d.addErrback(errback)
 
-    def historyGet(self, from_jid, to_jid, limit, between=True, filters='', profile="@NONE@", callback=None, errback=None):
-        d = self.root.callRemote("historyGet", from_jid, to_jid, limit, between, filters, profile)
+    def historyGet(
+        self,
+        from_jid,
+        to_jid,
+        limit,
+        between=True,
+        filters="",
+        profile="@NONE@",
+        callback=None,
+        errback=None,
+    ):
+        d = self.root.callRemote(
+            "historyGet", from_jid, to_jid, limit, between, filters, profile
+        )
         if callback is not None:
             d.addCallback(callback)
         if errback is None:
@@ -340,7 +441,9 @@
             errback = self._generic_errback
         d.addErrback(errback)
 
-    def launchAction(self, callback_id, data, profile_key="@DEFAULT@", callback=None, errback=None):
+    def launchAction(
+        self, callback_id, data, profile_key="@DEFAULT@", callback=None, errback=None
+    ):
         d = self.root.callRemote("launchAction", callback_id, data, profile_key)
         if callback is not None:
             d.addCallback(callback)
@@ -364,8 +467,19 @@
             errback = self._generic_errback
         d.addErrback(errback)
 
-    def menuLaunch(self, menu_type, path, data, security_limit, profile_key, callback=None, errback=None):
-        d = self.root.callRemote("menuLaunch", menu_type, path, data, security_limit, profile_key)
+    def menuLaunch(
+        self,
+        menu_type,
+        path,
+        data,
+        security_limit,
+        profile_key,
+        callback=None,
+        errback=None,
+    ):
+        d = self.root.callRemote(
+            "menuLaunch", menu_type, path, data, security_limit, profile_key
+        )
         if callback is not None:
             d.addCallback(callback)
         if errback is None:
@@ -380,8 +494,20 @@
             errback = self._generic_errback
         d.addErrback(errback)
 
-    def messageSend(self, to_jid, message, subject={}, mess_type="auto", extra={}, profile_key="@NONE@", callback=None, errback=None):
-        d = self.root.callRemote("messageSend", to_jid, message, subject, mess_type, extra, profile_key)
+    def messageSend(
+        self,
+        to_jid,
+        message,
+        subject={},
+        mess_type="auto",
+        extra={},
+        profile_key="@NONE@",
+        callback=None,
+        errback=None,
+    ):
+        d = self.root.callRemote(
+            "messageSend", to_jid, message, subject, mess_type, extra, profile_key
+        )
         if callback is not None:
             d.addCallback(lambda dummy: callback())
         if errback is None:
@@ -396,7 +522,9 @@
             errback = self._generic_errback
         d.addErrback(errback)
 
-    def paramsRegisterApp(self, xml, security_limit=-1, app='', callback=None, errback=None):
+    def paramsRegisterApp(
+        self, xml, security_limit=-1, app="", callback=None, errback=None
+    ):
         d = self.root.callRemote("paramsRegisterApp", xml, security_limit, app)
         if callback is not None:
             d.addCallback(lambda dummy: callback())
@@ -404,7 +532,9 @@
             errback = self._generic_errback
         d.addErrback(errback)
 
-    def profileCreate(self, profile, password='', component='', callback=None, errback=None):
+    def profileCreate(
+        self, profile, password="", component="", callback=None, errback=None
+    ):
         d = self.root.callRemote("profileCreate", profile, password, component)
         if callback is not None:
             d.addCallback(lambda dummy: callback())
@@ -412,7 +542,9 @@
             errback = self._generic_errback
         d.addErrback(errback)
 
-    def profileIsSessionStarted(self, profile_key="@DEFAULT@", callback=None, errback=None):
+    def profileIsSessionStarted(
+        self, profile_key="@DEFAULT@", callback=None, errback=None
+    ):
         d = self.root.callRemote("profileIsSessionStarted", profile_key)
         if callback is not None:
             d.addCallback(callback)
@@ -436,7 +568,9 @@
             errback = self._generic_errback
         d.addErrback(errback)
 
-    def profileStartSession(self, password='', profile_key="@DEFAULT@", callback=None, errback=None):
+    def profileStartSession(
+        self, password="", profile_key="@DEFAULT@", callback=None, errback=None
+    ):
         d = self.root.callRemote("profileStartSession", password, profile_key)
         if callback is not None:
             d.addCallback(callback)
@@ -444,7 +578,9 @@
             errback = self._generic_errback
         d.addErrback(errback)
 
-    def profilesListGet(self, clients=True, components=False, callback=None, errback=None):
+    def profilesListGet(
+        self, clients=True, components=False, callback=None, errback=None
+    ):
         d = self.root.callRemote("profilesListGet", clients, components)
         if callback is not None:
             d.addCallback(callback)
@@ -492,15 +628,34 @@
             errback = self._generic_errback
         d.addErrback(errback)
 
-    def setParam(self, name, value, category, security_limit=-1, profile_key="@DEFAULT@", callback=None, errback=None):
-        d = self.root.callRemote("setParam", name, value, category, security_limit, profile_key)
+    def setParam(
+        self,
+        name,
+        value,
+        category,
+        security_limit=-1,
+        profile_key="@DEFAULT@",
+        callback=None,
+        errback=None,
+    ):
+        d = self.root.callRemote(
+            "setParam", name, value, category, security_limit, profile_key
+        )
         if callback is not None:
             d.addCallback(lambda dummy: callback())
         if errback is None:
             errback = self._generic_errback
         d.addErrback(errback)
 
-    def setPresence(self, to_jid='', show='', statuses={}, profile_key="@DEFAULT@", callback=None, errback=None):
+    def setPresence(
+        self,
+        to_jid="",
+        show="",
+        statuses={},
+        profile_key="@DEFAULT@",
+        callback=None,
+        errback=None,
+    ):
         d = self.root.callRemote("setPresence", to_jid, show, statuses, profile_key)
         if callback is not None:
             d.addCallback(lambda dummy: callback())
@@ -508,7 +663,9 @@
             errback = self._generic_errback
         d.addErrback(errback)
 
-    def subscription(self, sub_type, entity, profile_key="@DEFAULT@", callback=None, errback=None):
+    def subscription(
+        self, sub_type, entity, profile_key="@DEFAULT@", callback=None, errback=None
+    ):
         d = self.root.callRemote("subscription", sub_type, entity, profile_key)
         if callback is not None:
             d.addCallback(lambda dummy: callback())
@@ -516,7 +673,15 @@
             errback = self._generic_errback
         d.addErrback(errback)
 
-    def updateContact(self, entity_jid, name, groups, profile_key="@DEFAULT@", callback=None, errback=None):
+    def updateContact(
+        self,
+        entity_jid,
+        name,
+        groups,
+        profile_key="@DEFAULT@",
+        callback=None,
+        errback=None,
+    ):
         d = self.root.callRemote("updateContact", entity_jid, name, groups, profile_key)
         if callback is not None:
             d.addCallback(lambda dummy: callback())
--- a/sat_frontends/jp/arg_tools.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/jp/arg_tools.py	Wed Jun 27 20:14:46 2018 +0200
@@ -26,9 +26,9 @@
 
     @param smart(bool): if True, only escape if needed
     """
-    if smart and not ' ' in arg and not '"' in arg:
+    if smart and not " " in arg and not '"' in arg:
         return arg
-    return u'"' + arg.replace(u'"',u'\\"') + u'"'
+    return u'"' + arg.replace(u'"', u'\\"') + u'"'
 
 
 def get_cmd_choices(cmd=None, parser=None):
@@ -61,7 +61,7 @@
     # else USE args would not work correctly (only for current parser)
     parser_args = []
     for arg in args:
-        if arg.startswith('-'):
+        if arg.startswith("-"):
             break
         try:
             parser = get_cmd_choices(arg, parser)
@@ -71,25 +71,31 @@
 
     # post_args are remaning given args,
     # without the ones corresponding to parsers
-    post_args = args[len(parser_args):]
+    post_args = args[len(parser_args) :]
 
     opt_args = []
     pos_args = []
     actions = {a.dest: a for a in parser._actions}
     for arg, value in use.iteritems():
         try:
-            if arg == u'item' and not u'item' in actions:
+            if arg == u"item" and not u"item" in actions:
                 # small hack when --item is appended to a --items list
-                arg = u'items'
+                arg = u"items"
             action = actions[arg]
         except KeyError:
             if verbose:
-                host.disp(_(u'ignoring {name}={value}, not corresponding to any argument (in USE)').format(
-                    name=arg,
-                    value=escape(value)))
+                host.disp(
+                    _(
+                        u"ignoring {name}={value}, not corresponding to any argument (in USE)"
+                    ).format(name=arg, value=escape(value))
+                )
         else:
             if verbose:
-                host.disp(_(u'arg {name}={value} (in USE)').format(name=arg, value=escape(value)))
+                host.disp(
+                    _(u"arg {name}={value} (in USE)").format(
+                        name=arg, value=escape(value)
+                    )
+                )
             if not action.option_strings:
                 pos_args.append(value)
             else:
--- a/sat_frontends/jp/cmd_account.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/jp/cmd_account.py	Wed Jun 27 20:14:46 2018 +0200
@@ -21,6 +21,7 @@
 
 from sat_frontends.jp.constants import Const as C
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from sat.core.i18n import _
 from sat_frontends.jp import base
@@ -30,103 +31,194 @@
 
 
 class AccountCreate(base.CommandBase):
-
     def __init__(self, host):
-        super(AccountCreate, self).__init__(host, 'create', use_profile=False, use_verbose=True, help=_(u'create a XMPP account'))
+        super(AccountCreate, self).__init__(
+            host,
+            "create",
+            use_profile=False,
+            use_verbose=True,
+            help=_(u"create a XMPP account"),
+        )
         self.need_loop = True
 
     def add_parser_options(self):
-        self.parser.add_argument('jid', type=base.unicode_decoder, help=_(u'jid to create'))
-        self.parser.add_argument('password', type=base.unicode_decoder, help=_(u'password of the account'))
-        self.parser.add_argument('-p', '--profile', type=base.unicode_decoder, help=_(u"create a profile to use this account (default: don't create profile)"))
-        self.parser.add_argument('-e', '--email', type=base.unicode_decoder, default="", help=_(u"email (usage depends of XMPP server)"))
-        self.parser.add_argument('-H', '--host', type=base.unicode_decoder, default="", help=_(u"server host (IP address or domain, default: use localhost)"))
-        self.parser.add_argument('-P', '--port', type=int, default=0, help=_(u"server port (IP address or domain, default: use localhost)"))
+        self.parser.add_argument(
+            "jid", type=base.unicode_decoder, help=_(u"jid to create")
+        )
+        self.parser.add_argument(
+            "password", type=base.unicode_decoder, help=_(u"password of the account")
+        )
+        self.parser.add_argument(
+            "-p",
+            "--profile",
+            type=base.unicode_decoder,
+            help=_(
+                u"create a profile to use this account (default: don't create profile)"
+            ),
+        )
+        self.parser.add_argument(
+            "-e",
+            "--email",
+            type=base.unicode_decoder,
+            default="",
+            help=_(u"email (usage depends of XMPP server)"),
+        )
+        self.parser.add_argument(
+            "-H",
+            "--host",
+            type=base.unicode_decoder,
+            default="",
+            help=_(u"server host (IP address or domain, default: use localhost)"),
+        )
+        self.parser.add_argument(
+            "-P",
+            "--port",
+            type=int,
+            default=0,
+            help=_(u"server port (IP address or domain, default: use localhost)"),
+        )
 
     def _setParamCb(self):
-        self.host.bridge.setParam("Password", self.args.password, "Connection", profile_key=self.args.profile, callback=self.host.quit, errback=self.errback)
+        self.host.bridge.setParam(
+            "Password",
+            self.args.password,
+            "Connection",
+            profile_key=self.args.profile,
+            callback=self.host.quit,
+            errback=self.errback,
+        )
 
     def _session_started(self, dummy):
-        self.host.bridge.setParam("JabberID", self.args.jid, "Connection", profile_key=self.args.profile, callback=self._setParamCb, errback=self.errback)
+        self.host.bridge.setParam(
+            "JabberID",
+            self.args.jid,
+            "Connection",
+            profile_key=self.args.profile,
+            callback=self._setParamCb,
+            errback=self.errback,
+        )
 
     def _profileCreateCb(self):
         self.disp(_(u"profile created"), 1)
-        self.host.bridge.profileStartSession(self.args.password, self.args.profile, callback=self._session_started, errback=self.errback)
+        self.host.bridge.profileStartSession(
+            self.args.password,
+            self.args.profile,
+            callback=self._session_started,
+            errback=self.errback,
+        )
 
     def _profileCreateEb(self, failure_):
-        self.disp(_(u"Can't create profile {profile} to associate with jid {jid}: {msg}").format(
-            profile = self.args.profile,
-            jid = self.args.jid,
-            msg = failure_), error=True)
+        self.disp(
+            _(
+                u"Can't create profile {profile} to associate with jid {jid}: {msg}"
+            ).format(profile=self.args.profile, jid=self.args.jid, msg=failure_),
+            error=True,
+        )
         self.host.quit(C.EXIT_BRIDGE_ERRBACK)
 
     def accountNewCb(self):
         self.disp(_(u"XMPP account created"), 1)
         if self.args.profile is not None:
             self.disp(_(u"creating profile"), 2)
-            self.host.bridge.profileCreate(self.args.profile, self.args.password, "", callback=self._profileCreateCb, errback=self._profileCreateEb)
+            self.host.bridge.profileCreate(
+                self.args.profile,
+                self.args.password,
+                "",
+                callback=self._profileCreateCb,
+                errback=self._profileCreateEb,
+            )
         else:
             self.host.quit()
 
     def accountNewEb(self, failure_):
-        self.disp(_(u"Can't create new account on server {host} with jid {jid}: {msg}").format(
-            host = self.args.host or u"localhost",
-            jid = self.args.jid,
-            msg = failure_), error=True)
+        self.disp(
+            _(u"Can't create new account on server {host} with jid {jid}: {msg}").format(
+                host=self.args.host or u"localhost", jid=self.args.jid, msg=failure_
+            ),
+            error=True,
+        )
         self.host.quit(C.EXIT_BRIDGE_ERRBACK)
 
     def start(self):
-        self.host.bridge.inBandAccountNew(self.args.jid, self.args.password, self.args.email, self.args.host, self.args.port,
-            callback=self.accountNewCb, errback=self.accountNewEb)
-
+        self.host.bridge.inBandAccountNew(
+            self.args.jid,
+            self.args.password,
+            self.args.email,
+            self.args.host,
+            self.args.port,
+            callback=self.accountNewCb,
+            errback=self.accountNewEb,
+        )
 
 
 class AccountModify(base.CommandBase):
-
     def __init__(self, host):
-        super(AccountModify, self).__init__(host, 'modify', help=_(u'change password for XMPP account'))
+        super(AccountModify, self).__init__(
+            host, "modify", help=_(u"change password for XMPP account")
+        )
         self.need_loop = True
 
     def add_parser_options(self):
-        self.parser.add_argument('password', type=base.unicode_decoder, help=_(u'new XMPP password'))
+        self.parser.add_argument(
+            "password", type=base.unicode_decoder, help=_(u"new XMPP password")
+        )
 
     def start(self):
-        self.host.bridge.inBandPasswordChange(self.args.password, self.args.profile,
-            callback=self.host.quit, errback=self.errback)
+        self.host.bridge.inBandPasswordChange(
+            self.args.password,
+            self.args.profile,
+            callback=self.host.quit,
+            errback=self.errback,
+        )
 
 
 class AccountDelete(base.CommandBase):
-
     def __init__(self, host):
-        super(AccountDelete, self).__init__(host, 'delete', help=_(u'delete a XMPP account'))
+        super(AccountDelete, self).__init__(
+            host, "delete", help=_(u"delete a XMPP account")
+        )
         self.need_loop = True
 
     def add_parser_options(self):
-        self.parser.add_argument('-f', '--force', action='store_true', help=_(u'delete account without confirmation'))
+        self.parser.add_argument(
+            "-f",
+            "--force",
+            action="store_true",
+            help=_(u"delete account without confirmation"),
+        )
 
     def _got_jid(self, jid_str):
         jid_ = jid.JID(jid_str)
         if not self.args.force:
-            message = (u"You are about to delete the XMPP account with jid {jid_}\n"
-                       u"This is the XMPP account of profile \"{profile}\"\n"
-                       u"Are you sure that you want to delete this account ?".format(
-                      jid_ = jid_,
-                      profile=self.profile
-                      ))
+            message = (
+                u"You are about to delete the XMPP account with jid {jid_}\n"
+                u'This is the XMPP account of profile "{profile}"\n'
+                u"Are you sure that you want to delete this account ?".format(
+                    jid_=jid_, profile=self.profile
+                )
+            )
             res = raw_input("{} (y/N)? ".format(message))
             if res not in ("y", "Y"):
                 self.disp(_(u"Account deletion cancelled"))
                 self.host.quit(2)
-        self.host.bridge.inBandUnregister(jid_.domain, self.args.profile,
-            callback=self.host.quit, errback=self.errback)
+        self.host.bridge.inBandUnregister(
+            jid_.domain, self.args.profile, callback=self.host.quit, errback=self.errback
+        )
 
     def start(self):
-        self.host.bridge.asyncGetParamA("JabberID", "Connection", profile_key=self.profile,
-                                        callback=self._got_jid, errback=self.errback)
+        self.host.bridge.asyncGetParamA(
+            "JabberID",
+            "Connection",
+            profile_key=self.profile,
+            callback=self._got_jid,
+            errback=self.errback,
+        )
 
 
 class Account(base.CommandBase):
     subcommands = (AccountCreate, AccountModify, AccountDelete)
 
     def __init__(self, host):
-        super(Account, self).__init__(host, 'account', use_profile=False, help=(u'XMPP account management'))
+        super(Account, self).__init__(
+            host, "account", use_profile=False, help=(u"XMPP account management")
+        )
--- a/sat_frontends/jp/cmd_adhoc.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/jp/cmd_adhoc.py	Wed Jun 27 20:14:46 2018 +0200
@@ -25,71 +25,134 @@
 
 __commands__ = ["AdHoc"]
 
-FLAG_LOOP = 'LOOP'
-MAGIC_BAREJID = '@PROFILE_BAREJID@'
+FLAG_LOOP = "LOOP"
+MAGIC_BAREJID = "@PROFILE_BAREJID@"
 
 
 class Remote(base.CommandBase):
     def __init__(self, host):
-        super(Remote, self).__init__(host, 'remote', use_verbose=True, help=_(u'remote control a software'))
+        super(Remote, self).__init__(
+            host, "remote", use_verbose=True, help=_(u"remote control a software")
+        )
 
     def add_parser_options(self):
         self.parser.add_argument("software", type=str, help=_(u"software name"))
-        self.parser.add_argument("-j", "--jids", type=base.unicode_decoder, nargs='*', default=[], help=_(u"jids allowed to use the command"))
-        self.parser.add_argument("-g", "--groups", type=base.unicode_decoder, nargs='*', default=[], help=_(u"groups allowed to use the command"))
-        self.parser.add_argument("--forbidden-groups", type=base.unicode_decoder, nargs='*', default=[], help=_(u"groups that are *NOT* allowed to use the command"))
-        self.parser.add_argument("--forbidden-jids", type=base.unicode_decoder, nargs='*', default=[], help=_(u"jids that are *NOT* allowed to use the command"))
-        self.parser.add_argument("-l", "--loop", action="store_true", help=_(u"loop on the commands"))
+        self.parser.add_argument(
+            "-j",
+            "--jids",
+            type=base.unicode_decoder,
+            nargs="*",
+            default=[],
+            help=_(u"jids allowed to use the command"),
+        )
+        self.parser.add_argument(
+            "-g",
+            "--groups",
+            type=base.unicode_decoder,
+            nargs="*",
+            default=[],
+            help=_(u"groups allowed to use the command"),
+        )
+        self.parser.add_argument(
+            "--forbidden-groups",
+            type=base.unicode_decoder,
+            nargs="*",
+            default=[],
+            help=_(u"groups that are *NOT* allowed to use the command"),
+        )
+        self.parser.add_argument(
+            "--forbidden-jids",
+            type=base.unicode_decoder,
+            nargs="*",
+            default=[],
+            help=_(u"jids that are *NOT* allowed to use the command"),
+        )
+        self.parser.add_argument(
+            "-l", "--loop", action="store_true", help=_(u"loop on the commands")
+        )
 
     def start(self):
         name = self.args.software.lower()
         flags = []
-        magics = {jid for jid in self.args.jids if jid.count('@')>1}
+        magics = {jid for jid in self.args.jids if jid.count("@") > 1}
         magics.add(MAGIC_BAREJID)
         jids = set(self.args.jids).difference(magics)
         if self.args.loop:
             flags.append(FLAG_LOOP)
-        bus_name, methods = self.host.bridge.adHocDBusAddAuto(name, jids, self.args.groups, magics,
-                                                              self.args.forbidden_jids, self.args.forbidden_groups,
-                                                              flags, self.profile)
+        bus_name, methods = self.host.bridge.adHocDBusAddAuto(
+            name,
+            jids,
+            self.args.groups,
+            magics,
+            self.args.forbidden_jids,
+            self.args.forbidden_groups,
+            flags,
+            self.profile,
+        )
         if not bus_name:
             self.disp(_("No bus name found"), 1)
             return
         self.disp(_("Bus name found: [%s]" % bus_name), 1)
         for method in methods:
             path, iface, command = method
-            self.disp(_("Command found: (path:%(path)s, iface: %(iface)s) [%(command)s]" % {'path': path,
-                                                                                         'iface': iface,
-                                                                                         'command': command
-                                                                                         }),1)
+            self.disp(
+                _(
+                    "Command found: (path:%(path)s, iface: %(iface)s) [%(command)s]"
+                    % {"path": path, "iface": iface, "command": command}
+                ),
+                1,
+            )
 
 
 class Run(base.CommandBase):
     """Run an Ad-Hoc command"""
 
     def __init__(self, host):
-        super(Run, self).__init__(host, 'run', use_verbose=True, help=_(u'run an Ad-Hoc command'))
-        self.need_loop=True
+        super(Run, self).__init__(
+            host, "run", use_verbose=True, help=_(u"run an Ad-Hoc command")
+        )
+        self.need_loop = True
 
     def add_parser_options(self):
-        self.parser.add_argument('-j', '--jid', type=base.unicode_decoder, default=u'', help=_(u"jid of the service (default: profile's server"))
-        self.parser.add_argument("-S", "--submit", action='append_const', const=xmlui_manager.SUBMIT, dest='workflow', help=_(u"submit form/page"))
-        self.parser.add_argument("-f",
+        self.parser.add_argument(
+            "-j",
+            "--jid",
+            type=base.unicode_decoder,
+            default=u"",
+            help=_(u"jid of the service (default: profile's server"),
+        )
+        self.parser.add_argument(
+            "-S",
+            "--submit",
+            action="append_const",
+            const=xmlui_manager.SUBMIT,
+            dest="workflow",
+            help=_(u"submit form/page"),
+        )
+        self.parser.add_argument(
+            "-f",
             "--field",
             type=base.unicode_decoder,
-            action='append',
+            action="append",
             nargs=2,
-            dest='workflow',
+            dest="workflow",
             metavar=(u"KEY", u"VALUE"),
-            help=_(u"field value"))
-        self.parser.add_argument('node', type=base.unicode_decoder, nargs='?', default=u'', help=_(u"node of the command (default: list commands)"))
+            help=_(u"field value"),
+        )
+        self.parser.add_argument(
+            "node",
+            type=base.unicode_decoder,
+            nargs="?",
+            default=u"",
+            help=_(u"node of the command (default: list commands)"),
+        )
 
     def adHocRunCb(self, xmlui_raw):
         xmlui = xmlui_manager.create(self.host, xmlui_raw)
         workflow = self.args.workflow
         xmlui.show(workflow)
         if not workflow:
-            if xmlui.type == 'form':
+            if xmlui.type == "form":
                 xmlui.submitForm()
             else:
                 self.host.quit()
@@ -100,20 +163,31 @@
             self.args.node,
             self.profile,
             callback=self.adHocRunCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't get ad-hoc commands list: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
+            errback=partial(
+                self.errback,
+                msg=_(u"can't get ad-hoc commands list: {}"),
+                exit_code=C.EXIT_BRIDGE_ERRBACK,
+            ),
+        )
 
 
 class List(base.CommandBase):
     """Run an Ad-Hoc command"""
 
     def __init__(self, host):
-        super(List, self).__init__(host, 'list', use_verbose=True, help=_(u'list Ad-Hoc commands of a service'))
-        self.need_loop=True
+        super(List, self).__init__(
+            host, "list", use_verbose=True, help=_(u"list Ad-Hoc commands of a service")
+        )
+        self.need_loop = True
 
     def add_parser_options(self):
-        self.parser.add_argument('-j', '--jid', type=base.unicode_decoder, default=u'', help=_(u"jid of the service (default: profile's server"))
+        self.parser.add_argument(
+            "-j",
+            "--jid",
+            type=base.unicode_decoder,
+            default=u"",
+            help=_(u"jid of the service (default: profile's server"),
+        )
 
     def adHocListCb(self, xmlui_raw):
         xmlui = xmlui_manager.create(self.host, xmlui_raw)
@@ -126,13 +200,18 @@
             self.args.jid,
             self.profile,
             callback=self.adHocListCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't get ad-hoc commands list: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
+            errback=partial(
+                self.errback,
+                msg=_(u"can't get ad-hoc commands list: {}"),
+                exit_code=C.EXIT_BRIDGE_ERRBACK,
+            ),
+        )
 
 
 class AdHoc(base.CommandBase):
     subcommands = (Run, List, Remote)
 
     def __init__(self, host):
-        super(AdHoc, self).__init__(host, 'ad-hoc', use_profile=False, help=_('Ad-hoc commands'))
+        super(AdHoc, self).__init__(
+            host, "ad-hoc", use_profile=False, help=_("Ad-hoc commands")
+        )
--- a/sat_frontends/jp/cmd_avatar.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/jp/cmd_avatar.py	Wed Jun 27 20:14:46 2018 +0200
@@ -28,16 +28,20 @@
 
 
 __commands__ = ["Avatar"]
-DISPLAY_CMD = ['xv', 'display', 'gwenview', 'showtell']
+DISPLAY_CMD = ["xv", "display", "gwenview", "showtell"]
 
 
 class Set(base.CommandBase):
     def __init__(self, host):
-        super(Set, self).__init__(host, 'set', use_verbose=True, help=_('set avatar of the profile'))
-        self.need_loop=True
+        super(Set, self).__init__(
+            host, "set", use_verbose=True, help=_("set avatar of the profile")
+        )
+        self.need_loop = True
 
     def add_parser_options(self):
-        self.parser.add_argument("image_path", type=str, help=_("path to the image to upload"))
+        self.parser.add_argument(
+            "image_path", type=str, help=_("path to the image to upload")
+        )
 
     def start(self):
         """Send files to jabber contact"""
@@ -46,30 +50,37 @@
             self.disp(_(u"file [{}] doesn't exist !").format(path), error=True)
             self.host.quit(1)
         path = os.path.abspath(path)
-        self.host.bridge.avatarSet(path, self.profile, callback=self._avatarCb, errback=self._avatarEb)
+        self.host.bridge.avatarSet(
+            path, self.profile, callback=self._avatarCb, errback=self._avatarEb
+        )
 
     def _avatarCb(self):
         self.disp(_("avatar has been set"), 1)
         self.host.quit()
 
     def _avatarEb(self, failure_):
-        self.disp(_("error while uploading avatar: {msg}").format(msg=failure_), error=True)
+        self.disp(
+            _("error while uploading avatar: {msg}").format(msg=failure_), error=True
+        )
         self.host.quit(C.EXIT_ERROR)
 
 
 class Get(base.CommandBase):
-
     def __init__(self, host):
-        super(Get, self).__init__(host, 'get', use_verbose=True, help=_('retrieve avatar of an entity'))
-        self.need_loop=True
+        super(Get, self).__init__(
+            host, "get", use_verbose=True, help=_("retrieve avatar of an entity")
+        )
+        self.need_loop = True
 
     def add_parser_options(self):
         self.parser.add_argument("jid", type=base.unicode_decoder, help=_("entity"))
-        self.parser.add_argument("-s", "--show", action="store_true", help=_(u"show avatar"))
+        self.parser.add_argument(
+            "-s", "--show", action="store_true", help=_(u"show avatar")
+        )
 
     def showImage(self, path):
         sat_conf = config.parseMainConf()
-        cmd = config.getConfig(sat_conf, 'jp', 'image_cmd')
+        cmd = config.getConfig(sat_conf, "jp", "image_cmd")
         cmds = [cmd] + DISPLAY_CMD if cmd else DISPLAY_CMD
         for cmd in cmds:
             try:
@@ -83,6 +94,7 @@
             # didn't worked with commands, we try our luck with webbrowser
             # in some cases, webbrowser can actually open the associated display program
             import webbrowser
+
             webbrowser.open(path)
 
     def _avatarGetCb(self, avatar_path):
@@ -101,11 +113,20 @@
         self.host.quit(C.EXIT_ERROR)
 
     def start(self):
-        self.host.bridge.avatarGet(self.args.jid, False, False, self.profile, callback=self._avatarGetCb, errback=self._avatarGetEb)
+        self.host.bridge.avatarGet(
+            self.args.jid,
+            False,
+            False,
+            self.profile,
+            callback=self._avatarGetCb,
+            errback=self._avatarGetEb,
+        )
 
 
 class Avatar(base.CommandBase):
     subcommands = (Set, Get)
 
     def __init__(self, host):
-        super(Avatar, self).__init__(host, 'avatar', use_profile=False, help=_('avatar uploading/retrieving'))
+        super(Avatar, self).__init__(
+            host, "avatar", use_profile=False, help=_("avatar uploading/retrieving")
+        )
--- a/sat_frontends/jp/cmd_blog.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/jp/cmd_blog.py	Wed Jun 27 20:14:46 2018 +0200
@@ -40,40 +40,48 @@
 
 __commands__ = ["Blog"]
 
-SYNTAX_XHTML = u'xhtml'
+SYNTAX_XHTML = u"xhtml"
 # extensions to use with known syntaxes
 SYNTAX_EXT = {
-    '': 'txt', # used when the syntax is not found
+    "": "txt",  # used when the syntax is not found
     SYNTAX_XHTML: "xhtml",
-    "markdown": "md"
-    }
+    "markdown": "md",
+}
 
 
-CONF_SYNTAX_EXT = u'syntax_ext_dict'
+CONF_SYNTAX_EXT = u"syntax_ext_dict"
 BLOG_TMP_DIR = u"blog"
 # key to remove from metadata tmp file if they exist
-KEY_TO_REMOVE_METADATA = ('id','content', 'content_xhtml', 'comments_node', 'comments_service', 'updated')
+KEY_TO_REMOVE_METADATA = (
+    "id",
+    "content",
+    "content_xhtml",
+    "comments_node",
+    "comments_service",
+    "updated",
+)
 
-URL_REDIRECT_PREFIX = 'url_redirect_'
+URL_REDIRECT_PREFIX = "url_redirect_"
 INOTIFY_INSTALL = '"pip install inotify"'
-MB_KEYS = (u"id",
-           u"url",
-           u"atom_id",
-           u"updated",
-           u"published",
-           u"language",
-           u"comments",  # this key is used for all comments* keys
-           u"tags",  # this key is used for all tag* keys
-           u"author",
-           u"author_jid",
-           u"author_email",
-           u"author_jid_verified",
-           u"content",
-           u"content_xhtml",
-           u"title",
-           u"title_xhtml",
-           )
-OUTPUT_OPT_NO_HEADER = u'no-header'
+MB_KEYS = (
+    u"id",
+    u"url",
+    u"atom_id",
+    u"updated",
+    u"published",
+    u"language",
+    u"comments",  # this key is used for all comments* keys
+    u"tags",  # this key is used for all tag* keys
+    u"author",
+    u"author_jid",
+    u"author_email",
+    u"author_jid_verified",
+    u"content",
+    u"content_xhtml",
+    u"title",
+    u"title_xhtml",
+)
+OUTPUT_OPT_NO_HEADER = u"no-header"
 
 
 def guessSyntaxFromPath(host, sat_conf, path):
@@ -84,9 +92,9 @@
     @return(unicode): syntax to use
     """
     # we first try to guess syntax with extension
-    ext = os.path.splitext(path)[1][1:] # we get extension without the '.'
+    ext = os.path.splitext(path)[1][1:]  # we get extension without the '.'
     if ext:
-        for k,v in SYNTAX_EXT.iteritems():
+        for k, v in SYNTAX_EXT.iteritems():
             if k and ext == v:
                 return k
 
@@ -98,39 +106,61 @@
     """handle common option for publising commands (Set and Edit)"""
 
     def add_parser_options(self):
-        self.parser.add_argument("-T", '--title', type=base.unicode_decoder, help=_(u"title of the item"))
-        self.parser.add_argument("-t", '--tag', type=base.unicode_decoder, action='append', help=_(u"tag (category) of your item"))
-        self.parser.add_argument("-C", "--comments", action='store_true', help=_(u"enable comments"))
-        self.parser.add_argument("-S", '--syntax', type=base.unicode_decoder, help=_(u"syntax to use (default: get profile's default syntax)"))
+        self.parser.add_argument(
+            "-T", "--title", type=base.unicode_decoder, help=_(u"title of the item")
+        )
+        self.parser.add_argument(
+            "-t",
+            "--tag",
+            type=base.unicode_decoder,
+            action="append",
+            help=_(u"tag (category) of your item"),
+        )
+        self.parser.add_argument(
+            "-C", "--comments", action="store_true", help=_(u"enable comments")
+        )
+        self.parser.add_argument(
+            "-S",
+            "--syntax",
+            type=base.unicode_decoder,
+            help=_(u"syntax to use (default: get profile's default syntax)"),
+        )
 
     def setMbDataContent(self, content, mb_data):
         if self.args.syntax is None:
             # default syntax has been used
-            mb_data['content_rich'] =  content
+            mb_data["content_rich"] = content
         elif self.current_syntax == SYNTAX_XHTML:
-            mb_data['content_xhtml'] = content
+            mb_data["content_xhtml"] = content
         else:
-            mb_data['content_xhtml'] = self.host.bridge.syntaxConvert(content, self.current_syntax, SYNTAX_XHTML, False, self.profile)
+            mb_data["content_xhtml"] = self.host.bridge.syntaxConvert(
+                content, self.current_syntax, SYNTAX_XHTML, False, self.profile
+            )
 
     def setMbDataFromArgs(self, mb_data):
         """set microblog metadata according to command line options
 
         if metadata already exist, it will be overwritten
         """
-        mb_data['allow_comments'] = C.boolConst(self.args.comments)
+        mb_data["allow_comments"] = C.boolConst(self.args.comments)
         if self.args.tag:
-            data_format.iter2dict('tag', self.args.tag, mb_data, check_conflict=False)
+            data_format.iter2dict("tag", self.args.tag, mb_data, check_conflict=False)
         if self.args.title is not None:
-            mb_data['title'] = self.args.title
+            mb_data["title"] = self.args.title
 
 
 class Set(base.CommandBase, BlogPublishCommon):
-
     def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'set', use_pubsub=True, pubsub_flags={C.SINGLE_ITEM},
-                                  help=_(u'publish a new blog item or update an existing one'))
+        base.CommandBase.__init__(
+            self,
+            host,
+            "set",
+            use_pubsub=True,
+            pubsub_flags={C.SINGLE_ITEM},
+            help=_(u"publish a new blog item or update an existing one"),
+        )
         BlogPublishCommon.__init__(self)
-        self.need_loop=True
+        self.need_loop = True
 
     def add_parser_options(self):
         BlogPublishCommon.add_parser_options(self)
@@ -143,7 +173,7 @@
         self.pubsub_item = self.args.item
         mb_data = {}
         self.setMbDataFromArgs(mb_data)
-        content = codecs.getreader('utf-8')(sys.stdin).read()
+        content = codecs.getreader("utf-8")(sys.stdin).read()
         self.setMbDataContent(content, mb_data)
 
         self.host.bridge.mbSend(
@@ -152,77 +182,111 @@
             mb_data,
             self.profile,
             callback=self.exitCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't send item: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
+            errback=partial(
+                self.errback,
+                msg=_(u"can't send item: {}"),
+                exit_code=C.EXIT_BRIDGE_ERRBACK,
+            ),
+        )
 
 
 class Get(base.CommandBase):
     TEMPLATE = u"blog/articles.html"
 
     def __init__(self, host):
-        extra_outputs = {'default': self.default_output,
-                         'fancy': self.fancy_output}
-        base.CommandBase.__init__(self, host, 'get', use_verbose=True, use_pubsub=True, pubsub_flags={C.MULTI_ITEMS},
-                                  use_output=C.OUTPUT_COMPLEX, extra_outputs=extra_outputs, help=_(u'get blog item(s)'))
-        self.need_loop=True
+        extra_outputs = {"default": self.default_output, "fancy": self.fancy_output}
+        base.CommandBase.__init__(
+            self,
+            host,
+            "get",
+            use_verbose=True,
+            use_pubsub=True,
+            pubsub_flags={C.MULTI_ITEMS},
+            use_output=C.OUTPUT_COMPLEX,
+            extra_outputs=extra_outputs,
+            help=_(u"get blog item(s)"),
+        )
+        self.need_loop = True
 
     def add_parser_options(self):
-        # TODO: a key(s) argument to select keys to display
-        self.parser.add_argument("-k", "--key", type=base.unicode_decoder, action='append', dest='keys',
-                                 help=_(u"microblog data key(s) to display (default: depend of verbosity)"))
+        #  TODO: a key(s) argument to select keys to display
+        self.parser.add_argument(
+            "-k",
+            "--key",
+            type=base.unicode_decoder,
+            action="append",
+            dest="keys",
+            help=_(u"microblog data key(s) to display (default: depend of verbosity)"),
+        )
         # TODO: add MAM filters
 
     def template_data_mapping(self, data):
-        return {u'items': data_objects.BlogItems(data)}
+        return {u"items": data_objects.BlogItems(data)}
 
     def format_comments(self, item, keys):
-        comments_data = data_format.dict2iterdict(u'comments', item, (u'node', u'service'), pop=True)
+        comments_data = data_format.dict2iterdict(
+            u"comments", item, (u"node", u"service"), pop=True
+        )
         lines = []
         for data in comments_data:
-            lines.append(data[u'comments'])
-            for k in (u'node', u'service'):
+            lines.append(data[u"comments"])
+            for k in (u"node", u"service"):
                 if OUTPUT_OPT_NO_HEADER in self.args.output_opts:
-                    header = u''
+                    header = u""
                 else:
-                    header = C.A_HEADER + k + u': ' + A.RESET
+                    header = C.A_HEADER + k + u": " + A.RESET
                 lines.append(header + data[k])
-        return u'\n'.join(lines)
+        return u"\n".join(lines)
 
     def format_tags(self, item, keys):
-        tags = data_format.dict2iter('tag', item, pop=True)
-        return u', '.join(tags)
+        tags = data_format.dict2iter("tag", item, pop=True)
+        return u", ".join(tags)
 
     def format_updated(self, item, keys):
-        return self.format_time(item['updated'])
+        return self.format_time(item["updated"])
 
     def format_published(self, item, keys):
-        return self.format_time(item['published'])
+        return self.format_time(item["published"])
 
     def format_url(self, item, keys):
-        return uri.buildXMPPUri(u'pubsub',
-                                subtype=u'microblog',
-                                path=self.metadata[u'service'],
-                                node=self.metadata[u'node'],
-                                item=item[u'id'])
+        return uri.buildXMPPUri(
+            u"pubsub",
+            subtype=u"microblog",
+            path=self.metadata[u"service"],
+            node=self.metadata[u"node"],
+            item=item[u"id"],
+        )
 
     def get_keys(self):
         """return keys to display according to verbosity or explicit key request"""
         verbosity = self.args.verbose
         if self.args.keys:
             if not set(MB_KEYS).issuperset(self.args.keys):
-                self.disp(u"following keys are invalid: {invalid}.\n"
-                          u"Valid keys are: {valid}.".format(
-                          invalid = u', '.join(set(self.args.keys).difference(MB_KEYS)),
-                          valid = u', '.join(sorted(MB_KEYS))),
-                          error=True)
+                self.disp(
+                    u"following keys are invalid: {invalid}.\n"
+                    u"Valid keys are: {valid}.".format(
+                        invalid=u", ".join(set(self.args.keys).difference(MB_KEYS)),
+                        valid=u", ".join(sorted(MB_KEYS)),
+                    ),
+                    error=True,
+                )
                 self.host.quit(C.EXIT_BAD_ARG)
             return self.args.keys
         else:
             if verbosity == 0:
-                return (u'title', u'content')
+                return (u"title", u"content")
             elif verbosity == 1:
-                return (u"title", u"tags", u"author", u"author_jid", u"author_email", u"author_jid_verified", u"published", u"updated", u"content")
+                return (
+                    u"title",
+                    u"tags",
+                    u"author",
+                    u"author_jid",
+                    u"author_email",
+                    u"author_jid_verified",
+                    u"published",
+                    u"updated",
+                    u"content",
+                )
             else:
                 return MB_KEYS
 
@@ -231,7 +295,7 @@
         items, self.metadata = data
         keys = self.get_keys()
 
-        # k_cb use format_[key] methods for complex formattings
+        #  k_cb use format_[key] methods for complex formattings
         k_cb = {}
         for k in keys:
             try:
@@ -245,18 +309,19 @@
                 if k not in item and k not in k_cb:
                     continue
                 if OUTPUT_OPT_NO_HEADER in self.args.output_opts:
-                    header = ''
+                    header = ""
                 else:
                     header = u"{k_fmt}{key}:{k_fmt_e} {sep}".format(
-                    k_fmt = C.A_HEADER,
-                    key = k,
-                    k_fmt_e = A.RESET,
-                    sep = u'\n' if 'content' in k else u'')
+                        k_fmt=C.A_HEADER,
+                        key=k,
+                        k_fmt_e=A.RESET,
+                        sep=u"\n" if "content" in k else u"",
+                    )
                 value = k_cb[k](item, keys) if k in k_cb else item[k]
                 self.disp(header + value)
             # we want a separation line after each item but the last one
-            if idx < len(items)-1:
-                print(u'')
+            if idx < len(items) - 1:
+                print(u"")
 
     def format_time(self, timestamp):
         """return formatted date for timestamp
@@ -273,53 +338,53 @@
         this output doesn't use keys filter
         """
         # thanks to http://stackoverflow.com/a/943921
-        rows, columns = map(int, os.popen('stty size', 'r').read().split())
+        rows, columns = map(int, os.popen("stty size", "r").read().split())
         items, metadata = data
         verbosity = self.args.verbose
-        sep = A.color(A.FG_BLUE, columns * u'▬')
+        sep = A.color(A.FG_BLUE, columns * u"▬")
         if items:
-            print(u'\n' + sep  + '\n')
+            print(u"\n" + sep + "\n")
 
         for idx, item in enumerate(items):
-            title = item.get(u'title')
+            title = item.get(u"title")
             if verbosity > 0:
-                author = item[u'author']
-                published, updated = item[u'published'], item.get('updated')
+                author = item[u"author"]
+                published, updated = item[u"published"], item.get("updated")
             else:
                 author = published = updated = None
             if verbosity > 1:
-                tags = list(data_format.dict2iter('tag', item, pop=True))
+                tags = list(data_format.dict2iter("tag", item, pop=True))
             else:
                 tags = None
-            content = item.get(u'content')
+            content = item.get(u"content")
 
             if title:
-                print(A.color(A.BOLD, A.FG_CYAN, item[u'title']))
+                print(A.color(A.BOLD, A.FG_CYAN, item[u"title"]))
             meta = []
             if author:
                 meta.append(A.color(A.FG_YELLOW, author))
             if published:
-                meta.append(A.color(A.FG_YELLOW, u'on ', self.format_time(published)))
+                meta.append(A.color(A.FG_YELLOW, u"on ", self.format_time(published)))
             if updated != published:
-                 meta.append(A.color(A.FG_YELLOW, u'(updated on ', self.format_time(updated), u')'))
-            print(u' '.join(meta))
+                meta.append(
+                    A.color(A.FG_YELLOW, u"(updated on ", self.format_time(updated), u")")
+                )
+            print(u" ".join(meta))
             if tags:
-                print(A.color(A.FG_MAGENTA, u', '.join(tags)))
+                print(A.color(A.FG_MAGENTA, u", ".join(tags)))
             if (title or tags) and content:
                 print("")
             if content:
                 self.disp(content)
 
-            print(u'\n' + sep  + '\n')
-
+            print(u"\n" + sep + "\n")
 
     def mbGetCb(self, mb_result):
         self.output(mb_result)
         self.host.quit(C.EXIT_OK)
 
     def mbGetEb(self, failure_):
-        self.disp(u"can't get blog items: {reason}".format(
-            reason=failure_), error=True)
+        self.disp(u"can't get blog items: {reason}".format(reason=failure_), error=True)
         self.host.quit(C.EXIT_BRIDGE_ERRBACK)
 
     def start(self):
@@ -331,26 +396,41 @@
             {},
             self.profile,
             callback=self.mbGetCb,
-            errback=self.mbGetEb)
+            errback=self.mbGetEb,
+        )
 
 
 class Edit(base.CommandBase, BlogPublishCommon, common.BaseEdit):
-
     def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'edit', use_pubsub=True, pubsub_flags={C.SINGLE_ITEM},
-                                  use_draft=True, use_verbose=True, help=_(u'edit an existing or new blog post'))
+        base.CommandBase.__init__(
+            self,
+            host,
+            "edit",
+            use_pubsub=True,
+            pubsub_flags={C.SINGLE_ITEM},
+            use_draft=True,
+            use_verbose=True,
+            help=_(u"edit an existing or new blog post"),
+        )
         BlogPublishCommon.__init__(self)
         common.BaseEdit.__init__(self, self.host, BLOG_TMP_DIR, use_metadata=True)
 
     @property
     def current_syntax(self):
         if self._current_syntax is None:
-            self._current_syntax = self.host.bridge.getParamA("Syntax", "Composition", "value", self.profile)
+            self._current_syntax = self.host.bridge.getParamA(
+                "Syntax", "Composition", "value", self.profile
+            )
         return self._current_syntax
 
     def add_parser_options(self):
         BlogPublishCommon.add_parser_options(self)
-        self.parser.add_argument("-P", "--preview", action="store_true", help=_(u"launch a blog preview in parallel"))
+        self.parser.add_argument(
+            "-P",
+            "--preview",
+            action="store_true",
+            help=_(u"launch a blog preview in parallel"),
+        )
 
     def buildMetadataFile(self, content_file_path, mb_data=None):
         """Build a metadata file using json
@@ -367,11 +447,15 @@
         if os.path.exists(meta_file_path):
             self.disp(u"Metadata file already exists, we re-use it")
             try:
-                with open(meta_file_path, 'rb') as f:
+                with open(meta_file_path, "rb") as f:
                     mb_data = json.load(f)
             except (OSError, IOError, ValueError) as e:
-                self.disp(u"Can't read existing metadata file at {path}, aborting: {reason}".format(
-                    path=meta_file_path, reason=e), error=True)
+                self.disp(
+                    u"Can't read existing metadata file at {path}, aborting: {reason}".format(
+                        path=meta_file_path, reason=e
+                    ),
+                    error=True,
+                )
                 self.host.quit(1)
         else:
             mb_data = {} if mb_data is None else mb_data.copy()
@@ -387,15 +471,22 @@
 
         # then we create the file and write metadata there, as JSON dict
         # XXX: if we port jp one day on Windows, O_BINARY may need to be added here
-        with os.fdopen(os.open(meta_file_path, os.O_RDWR | os.O_CREAT | os.O_TRUNC,0o600), 'w+b') as f:
+        with os.fdopen(
+            os.open(meta_file_path, os.O_RDWR | os.O_CREAT | os.O_TRUNC, 0o600), "w+b"
+        ) as f:
             # we need to use an intermediate unicode buffer to write to the file unicode without escaping characters
-            unicode_dump = json.dumps(mb_data, ensure_ascii=False, indent=4, separators=(',', ': '), sort_keys=True)
-            f.write(unicode_dump.encode('utf-8'))
+            unicode_dump = json.dumps(
+                mb_data,
+                ensure_ascii=False,
+                indent=4,
+                separators=(",", ": "),
+                sort_keys=True,
+            )
+            f.write(unicode_dump.encode("utf-8"))
 
         return mb_data, meta_file_path
 
-    def edit(self, content_file_path, content_file_obj,
-             mb_data=None):
+    def edit(self, content_file_path, content_file_obj, mb_data=None):
         """Edit the file contening the content using editor, and publish it"""
         # we first create metadata file
         meta_ori, meta_file_path = self.buildMetadataFile(content_file_path, mb_data)
@@ -405,37 +496,61 @@
             self.disp(u"Preview requested, launching it", 1)
             # we redirect outputs to /dev/null to avoid console pollution in editor
             # if user wants to see messages, (s)he can call "blog preview" directly
-            DEVNULL = open(os.devnull, 'wb')
-            subprocess.Popen([sys.argv[0], "blog", "preview", "--inotify", "true", "-p", self.profile, content_file_path], stdout=DEVNULL, stderr=subprocess.STDOUT)
+            DEVNULL = open(os.devnull, "wb")
+            subprocess.Popen(
+                [
+                    sys.argv[0],
+                    "blog",
+                    "preview",
+                    "--inotify",
+                    "true",
+                    "-p",
+                    self.profile,
+                    content_file_path,
+                ],
+                stdout=DEVNULL,
+                stderr=subprocess.STDOUT,
+            )
 
         # we launch editor
-        self.runEditor("blog_editor_args", content_file_path, content_file_obj, meta_file_path=meta_file_path, meta_ori=meta_ori)
+        self.runEditor(
+            "blog_editor_args",
+            content_file_path,
+            content_file_obj,
+            meta_file_path=meta_file_path,
+            meta_ori=meta_ori,
+        )
 
     def publish(self, content, mb_data):
         self.setMbDataContent(content, mb_data)
 
         if self.pubsub_item is not None:
-            mb_data['id'] = self.pubsub_item
+            mb_data["id"] = self.pubsub_item
 
-        self.host.bridge.mbSend(self.pubsub_service, self.pubsub_node, mb_data, self.profile)
+        self.host.bridge.mbSend(
+            self.pubsub_service, self.pubsub_node, mb_data, self.profile
+        )
         self.disp(u"Blog item published")
 
-
     def getTmpSuff(self):
         # we get current syntax to determine file extension
-        return SYNTAX_EXT.get(self.current_syntax, SYNTAX_EXT[''])
+        return SYNTAX_EXT.get(self.current_syntax, SYNTAX_EXT[""])
 
     def getItemData(self, service, node, item):
         items = [item] if item is not None else []
-        mb_data =  self.host.bridge.mbGet(service, node, 1, items, {}, self.profile)[0][0]
+        mb_data = self.host.bridge.mbGet(service, node, 1, items, {}, self.profile)[0][0]
         try:
-            content = mb_data['content_xhtml']
+            content = mb_data["content_xhtml"]
         except KeyError:
-            content = mb_data['content']
+            content = mb_data["content"]
             if content:
-                content = self.host.bridge.syntaxConvert(content, 'text', SYNTAX_XHTML, False, self.profile)
+                content = self.host.bridge.syntaxConvert(
+                    content, "text", SYNTAX_XHTML, False, self.profile
+                )
         if content and self.current_syntax != SYNTAX_XHTML:
-            content = self.host.bridge.syntaxConvert(content, SYNTAX_XHTML, self.current_syntax, False, self.profile)
+            content = self.host.bridge.syntaxConvert(
+                content, SYNTAX_XHTML, self.current_syntax, False, self.profile
+            )
         if content and self.current_syntax == SYNTAX_XHTML:
             try:
                 from lxml import etree
@@ -446,22 +561,37 @@
                 root = etree.fromstring(content, parser)
                 content = etree.tostring(root, encoding=unicode, pretty_print=True)
 
-        return content, mb_data, mb_data['id']
+        return content, mb_data, mb_data["id"]
 
     def start(self):
         # if there are user defined extension, we use them
-        SYNTAX_EXT.update(config.getConfig(self.sat_conf, 'jp', CONF_SYNTAX_EXT, {}))
+        SYNTAX_EXT.update(config.getConfig(self.sat_conf, "jp", CONF_SYNTAX_EXT, {}))
         self._current_syntax = self.args.syntax
         if self._current_syntax is not None:
             try:
-                self._current_syntax = self.args.syntax = self.host.bridge.syntaxGet(self.current_syntax)
+                self._current_syntax = self.args.syntax = self.host.bridge.syntaxGet(
+                    self.current_syntax
+                )
             except Exception as e:
-                if "NotFound" in unicode(e):  # FIXME: there is not good way to check bridge errors
-                    self.parser.error(_(u"unknown syntax requested ({syntax})").format(syntax=self.args.syntax))
+                if "NotFound" in unicode(
+                    e
+                ):  #  FIXME: there is not good way to check bridge errors
+                    self.parser.error(
+                        _(u"unknown syntax requested ({syntax})").format(
+                            syntax=self.args.syntax
+                        )
+                    )
                 else:
                     raise e
 
-        self.pubsub_service, self.pubsub_node, self.pubsub_item, content_file_path, content_file_obj, mb_data = self.getItemPath()
+        (
+            self.pubsub_service,
+            self.pubsub_node,
+            self.pubsub_item,
+            content_file_path,
+            content_file_obj,
+            mb_data,
+        ) = self.getItemPath()
 
         self.edit(content_file_path, content_file_obj, mb_data=mb_data)
 
@@ -470,23 +600,42 @@
     # TODO: need to be rewritten with template output
 
     def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'preview', use_verbose=True, help=_(u'preview a blog content'))
+        base.CommandBase.__init__(
+            self, host, "preview", use_verbose=True, help=_(u"preview a blog content")
+        )
 
     def add_parser_options(self):
-        self.parser.add_argument("--inotify", type=str, choices=('auto', 'true', 'false'), default=u'auto', help=_(u"use inotify to handle preview"))
-        self.parser.add_argument("file", type=base.unicode_decoder, nargs='?', default=u'current', help=_(u"path to the content file"))
+        self.parser.add_argument(
+            "--inotify",
+            type=str,
+            choices=("auto", "true", "false"),
+            default=u"auto",
+            help=_(u"use inotify to handle preview"),
+        )
+        self.parser.add_argument(
+            "file",
+            type=base.unicode_decoder,
+            nargs="?",
+            default=u"current",
+            help=_(u"path to the content file"),
+        )
 
     def showPreview(self):
         # we implement showPreview here so we don't have to import webbrowser and urllib
         # when preview is not used
-        url = 'file:{}'.format(self.urllib.quote(self.preview_file_path))
+        url = "file:{}".format(self.urllib.quote(self.preview_file_path))
         self.webbrowser.open_new_tab(url)
 
     def _launchPreviewExt(self, cmd_line, opt_name):
-        url = 'file:{}'.format(self.urllib.quote(self.preview_file_path))
-        args = common.parse_args(self.host, cmd_line, url=url, preview_file=self.preview_file_path)
+        url = "file:{}".format(self.urllib.quote(self.preview_file_path))
+        args = common.parse_args(
+            self.host, cmd_line, url=url, preview_file=self.preview_file_path
+        )
         if not args:
-            self.disp(u"Couln't find command in \"{name}\", abording".format(name=opt_name), error=True)
+            self.disp(
+                u'Couln\'t find command in "{name}", abording'.format(name=opt_name),
+                error=True,
+            )
             self.host.quit(1)
         subprocess.Popen(args)
 
@@ -497,91 +646,121 @@
         self._launchPreviewExt(self.update_cb_cmd, "blog_preview_update_cmd")
 
     def updateContent(self):
-        with open(self.content_file_path, 'rb') as f:
-            content = f.read().decode('utf-8-sig')
+        with open(self.content_file_path, "rb") as f:
+            content = f.read().decode("utf-8-sig")
             if content and self.syntax != SYNTAX_XHTML:
-                # we use safe=True because we want to have a preview as close as possible to what the
-                # people will see
-                content = self.host.bridge.syntaxConvert(content, self.syntax, SYNTAX_XHTML, True, self.profile)
+                # we use safe=True because we want to have a preview as close as possible
+                # to what the people will see
+                content = self.host.bridge.syntaxConvert(
+                    content, self.syntax, SYNTAX_XHTML, True, self.profile
+                )
 
-        xhtml = (u'<html xmlns="http://www.w3.org/1999/xhtml">' +
-                 u'<head><meta http-equiv="Content-Type" content="text/html;charset=utf-8" /></head>'+
-                 '<body>{}</body>' +
-                 u'</html>').format(content)
+        xhtml = (
+            u'<html xmlns="http://www.w3.org/1999/xhtml">'
+            u'<head><meta http-equiv="Content-Type" content="text/html;charset=utf-8" />'
+            u"</head>"
+            u"<body>{}</body>"
+            u"</html>"
+        ).format(content)
 
-        with open(self.preview_file_path, 'wb') as f:
-            f.write(xhtml.encode('utf-8'))
+        with open(self.preview_file_path, "wb") as f:
+            f.write(xhtml.encode("utf-8"))
 
     def start(self):
         import webbrowser
         import urllib
+
         self.webbrowser, self.urllib = webbrowser, urllib
 
-        if self.args.inotify != 'false':
+        if self.args.inotify != "false":
             try:
                 import inotify.adapters
                 import inotify.constants
                 from inotify.calls import InotifyError
             except ImportError:
-                if self.args.inotify == 'auto':
+                if self.args.inotify == "auto":
                     inotify = None
-                    self.disp(u'inotify module not found, deactivating feature. You can install it with {install}'.format(install=INOTIFY_INSTALL))
+                    self.disp(
+                        u"inotify module not found, deactivating feature. You can install"
+                        u" it with {install}".format(install=INOTIFY_INSTALL)
+                    )
                 else:
-                    self.disp(u"inotify not found, can't activate the feature! Please install it with {install}".format(install=INOTIFY_INSTALL), error=True)
+                    self.disp(
+                        u"inotify not found, can't activate the feature! Please install "
+                        u"it with {install}".format(install=INOTIFY_INSTALL),
+                        error=True,
+                    )
                     self.host.quit(1)
             else:
                 # we deactivate logging in inotify, which is quite annoying
                 try:
                     inotify.adapters._LOGGER.setLevel(40)
                 except AttributeError:
-                    self.disp(u"Logger doesn't exists, inotify may have chanded", error=True)
+                    self.disp(
+                        u"Logger doesn't exists, inotify may have chanded", error=True
+                    )
         else:
-            inotify=None
+            inotify = None
 
         sat_conf = config.parseMainConf()
-        SYNTAX_EXT.update(config.getConfig(sat_conf, 'jp', CONF_SYNTAX_EXT, {}))
+        SYNTAX_EXT.update(config.getConfig(sat_conf, "jp", CONF_SYNTAX_EXT, {}))
 
         try:
-            self.open_cb_cmd = config.getConfig(sat_conf, 'jp', "blog_preview_open_cmd", Exception)
+            self.open_cb_cmd = config.getConfig(
+                sat_conf, "jp", "blog_preview_open_cmd", Exception
+            )
         except (NoOptionError, NoSectionError):
             self.open_cb_cmd = None
             open_cb = self.showPreview
         else:
             open_cb = self.openPreviewExt
 
-        self.update_cb_cmd = config.getConfig(sat_conf, 'jp', "blog_preview_update_cmd", self.open_cb_cmd)
+        self.update_cb_cmd = config.getConfig(
+            sat_conf, "jp", "blog_preview_update_cmd", self.open_cb_cmd
+        )
         if self.update_cb_cmd is None:
             update_cb = self.showPreview
         else:
             update_cb = self.updatePreviewExt
 
         # which file do we need to edit?
-        if self.args.file == 'current':
+        if self.args.file == "current":
             self.content_file_path = self.getCurrentFile(sat_conf, self.profile)
         else:
             self.content_file_path = os.path.abspath(self.args.file)
 
         self.syntax = guessSyntaxFromPath(self.host, sat_conf, self.content_file_path)
 
-
         # at this point the syntax is converted, we can display the preview
-        preview_file = tempfile.NamedTemporaryFile(suffix='.xhtml', delete=False)
+        preview_file = tempfile.NamedTemporaryFile(suffix=".xhtml", delete=False)
         self.preview_file_path = preview_file.name
         preview_file.close()
         self.updateContent()
 
         if inotify is None:
-            # XXX: we don't delete file automatically because browser need it (and webbrowser.open can return before it is read)
-            self.disp(u'temporary file created at {}\nthis file will NOT BE DELETED AUTOMATICALLY, please delete it yourself when you have finished'.format(self.preview_file_path))
+            # XXX: we don't delete file automatically because browser need it
+            #      (and webbrowser.open can return before it is read)
+            self.disp(
+                u"temporary file created at {}\nthis file will NOT BE DELETED "
+                u"AUTOMATICALLY, please delete it yourself when you have finished".format(
+                    self.preview_file_path
+                )
+            )
             open_cb()
         else:
             open_cb()
-            i = inotify.adapters.Inotify(block_duration_s=60) # no need for 1 s duraction, inotify drive actions here
+            i = inotify.adapters.Inotify(
+                block_duration_s=60
+            )  # no need for 1 s duraction, inotify drive actions here
 
             def add_watch():
-                i.add_watch(self.content_file_path, mask=inotify.constants.IN_CLOSE_WRITE |
-                                                         inotify.constants.IN_DELETE_SELF |
-                                                         inotify.constants.IN_MOVE_SELF)
+                i.add_watch(
+                    self.content_file_path,
+                    mask=inotify.constants.IN_CLOSE_WRITE
+                    | inotify.constants.IN_DELETE_SELF
+                    | inotify.constants.IN_MOVE_SELF,
+                )
+
             add_watch()
 
             try:
@@ -589,7 +768,12 @@
                     if event is not None:
                         self.disp(u"Content updated", 1)
                         if {"IN_DELETE_SELF", "IN_MOVE_SELF"}.intersection(event[1]):
-                            self.disp(u"{} event catched, changing the watch".format(", ".join(event[1])), 2)
+                            self.disp(
+                                u"{} event catched, changing the watch".format(
+                                    ", ".join(event[1])
+                                ),
+                                2,
+                            )
                             try:
                                 add_watch()
                             except InotifyError:
@@ -600,7 +784,9 @@
                         self.updateContent()
                         update_cb()
             except InotifyError:
-                self.disp(u"Can't catch inotify events, as the file been deleted?", error=True)
+                self.disp(
+                    u"Can't catch inotify events, as the file been deleted?", error=True
+                )
             finally:
                 os.unlink(self.preview_file_path)
                 try:
@@ -611,81 +797,174 @@
 
 class Import(base.CommandAnswering):
     def __init__(self, host):
-        super(Import, self).__init__(host, 'import', use_pubsub=True, use_progress=True, help=_(u'import an external blog'))
-        self.need_loop=True
+        super(Import, self).__init__(
+            host,
+            "import",
+            use_pubsub=True,
+            use_progress=True,
+            help=_(u"import an external blog"),
+        )
+        self.need_loop = True
 
     def add_parser_options(self):
-        self.parser.add_argument("importer", type=base.unicode_decoder, nargs='?', help=_(u"importer name, nothing to display importers list"))
-        self.parser.add_argument('--host', type=base.unicode_decoder, help=_(u"original blog host"))
-        self.parser.add_argument('--no-images-upload', action='store_true', help=_(u"do *NOT* upload images (default: do upload images)"))
-        self.parser.add_argument('--upload-ignore-host', help=_(u"do not upload images from this host (default: upload all images)"))
-        self.parser.add_argument("--ignore-tls-errors", action="store_true", help=_("ignore invalide TLS certificate for uploads"))
-        self.parser.add_argument('-o', '--option', action='append', nargs=2, default=[], metavar=(u'NAME', u'VALUE'),
-            help=_(u"importer specific options (see importer description)"))
-        self.parser.add_argument("location", type=base.unicode_decoder, nargs='?',
-            help=_(u"importer data location (see importer description), nothing to show importer description"))
+        self.parser.add_argument(
+            "importer",
+            type=base.unicode_decoder,
+            nargs="?",
+            help=_(u"importer name, nothing to display importers list"),
+        )
+        self.parser.add_argument(
+            "--host", type=base.unicode_decoder, help=_(u"original blog host")
+        )
+        self.parser.add_argument(
+            "--no-images-upload",
+            action="store_true",
+            help=_(u"do *NOT* upload images (default: do upload images)"),
+        )
+        self.parser.add_argument(
+            "--upload-ignore-host",
+            help=_(u"do not upload images from this host (default: upload all images)"),
+        )
+        self.parser.add_argument(
+            "--ignore-tls-errors",
+            action="store_true",
+            help=_("ignore invalide TLS certificate for uploads"),
+        )
+        self.parser.add_argument(
+            "-o",
+            "--option",
+            action="append",
+            nargs=2,
+            default=[],
+            metavar=(u"NAME", u"VALUE"),
+            help=_(u"importer specific options (see importer description)"),
+        )
+        self.parser.add_argument(
+            "location",
+            type=base.unicode_decoder,
+            nargs="?",
+            help=_(
+                u"importer data location (see importer description), nothing to show "
+                u"importer description"
+            ),
+        )
 
     def onProgressStarted(self, metadata):
-        self.disp(_(u'Blog upload started'),2)
+        self.disp(_(u"Blog upload started"), 2)
 
     def onProgressFinished(self, metadata):
-        self.disp(_(u'Blog uploaded successfully'),2)
-        redirections = {k[len(URL_REDIRECT_PREFIX):]:v for k,v in metadata.iteritems()
-            if k.startswith(URL_REDIRECT_PREFIX)}
+        self.disp(_(u"Blog uploaded successfully"), 2)
+        redirections = {
+            k[len(URL_REDIRECT_PREFIX) :]: v
+            for k, v in metadata.iteritems()
+            if k.startswith(URL_REDIRECT_PREFIX)
+        }
         if redirections:
-            conf = u'\n'.join([
-                u'url_redirections_profile = {}'.format(self.profile),
-                u"url_redirections_dict = {}".format(
-                # we need to add ' ' before each new line and to double each '%' for ConfigParser
-                u'\n '.join(json.dumps(redirections, indent=1, separators=(',',': ')).replace(u'%', u'%%').split(u'\n'))),
-                ])
-            self.disp(_(u'\nTo redirect old URLs to new ones, put the following lines in your sat.conf file, in [libervia] section:\n\n{conf}'.format(conf=conf)))
+            conf = u"\n".join(
+                [
+                    u"url_redirections_profile = {}".format(self.profile),
+                    u"url_redirections_dict = {}".format(
+                        # we need to add ' ' before each new line
+                        # and to double each '%' for ConfigParser
+                        u"\n ".join(
+                            json.dumps(redirections, indent=1, separators=(",", ": "))
+                            .replace(u"%", u"%%")
+                            .split(u"\n")
+                        )
+                    ),
+                ]
+            )
+            self.disp(
+                _(
+                    u"\nTo redirect old URLs to new ones, put the following lines in your"
+                    u" sat.conf file, in [libervia] section:\n\n{conf}".format(conf=conf)
+                )
+            )
 
     def onProgressError(self, error_msg):
-        self.disp(_(u'Error while uploading blog: {}').format(error_msg),error=True)
+        self.disp(_(u"Error while uploading blog: {}").format(error_msg), error=True)
 
     def error(self, failure):
-        self.disp(_("Error while trying to upload a blog: {reason}").format(reason=failure), error=True)
+        self.disp(
+            _("Error while trying to upload a blog: {reason}").format(reason=failure),
+            error=True,
+        )
         self.host.quit(1)
 
     def start(self):
         if self.args.location is None:
-            for name in ('option', 'service', 'no_images_upload'):
+            for name in ("option", "service", "no_images_upload"):
                 if getattr(self.args, name):
-                    self.parser.error(_(u"{name} argument can't be used without location argument").format(name=name))
+                    self.parser.error(
+                        _(
+                            u"{name} argument can't be used without location argument"
+                        ).format(name=name)
+                    )
             if self.args.importer is None:
-                self.disp(u'\n'.join([u'{}: {}'.format(name, desc) for name, desc in self.host.bridge.blogImportList()]))
+                self.disp(
+                    u"\n".join(
+                        [
+                            u"{}: {}".format(name, desc)
+                            for name, desc in self.host.bridge.blogImportList()
+                        ]
+                    )
+                )
             else:
                 try:
-                    short_desc, long_desc = self.host.bridge.blogImportDesc(self.args.importer)
+                    short_desc, long_desc = self.host.bridge.blogImportDesc(
+                        self.args.importer
+                    )
                 except Exception as e:
-                    msg = [l for l in unicode(e).split('\n') if l][-1] # we only keep the last line
+                    msg = [l for l in unicode(e).split("\n") if l][
+                        -1
+                    ]  # we only keep the last line
                     self.disp(msg)
                     self.host.quit(1)
                 else:
-                    self.disp(u"{name}: {short_desc}\n\n{long_desc}".format(name=self.args.importer, short_desc=short_desc, long_desc=long_desc))
+                    self.disp(
+                        u"{name}: {short_desc}\n\n{long_desc}".format(
+                            name=self.args.importer,
+                            short_desc=short_desc,
+                            long_desc=long_desc,
+                        )
+                    )
             self.host.quit()
         else:
             # we have a location, an import is requested
             options = {key: value for key, value in self.args.option}
             if self.args.host:
-                options['host'] = self.args.host
+                options["host"] = self.args.host
             if self.args.ignore_tls_errors:
-                options['ignore_tls_errors'] = C.BOOL_TRUE
+                options["ignore_tls_errors"] = C.BOOL_TRUE
             if self.args.no_images_upload:
-                options['upload_images'] = C.BOOL_FALSE
+                options["upload_images"] = C.BOOL_FALSE
                 if self.args.upload_ignore_host:
-                    self.parser.error(u"upload-ignore-host option can't be used when no-images-upload is set")
+                    self.parser.error(
+                        u"upload-ignore-host option can't be used when no-images-upload "
+                        u"is set"
+                    )
             elif self.args.upload_ignore_host:
-                options['upload_ignore_host'] = self.args.upload_ignore_host
+                options["upload_ignore_host"] = self.args.upload_ignore_host
+
             def gotId(id_):
                 self.progress_id = id_
-            self.host.bridge.blogImport(self.args.importer, self.args.location, options, self.args.service, self.args.node, self.profile,
-                callback=gotId, errback=self.error)
+
+            self.host.bridge.blogImport(
+                self.args.importer,
+                self.args.location,
+                options,
+                self.args.service,
+                self.args.node,
+                self.profile,
+                callback=gotId,
+                errback=self.error,
+            )
 
 
 class Blog(base.CommandBase):
     subcommands = (Set, Get, Edit, Preview, Import)
 
     def __init__(self, host):
-        super(Blog, self).__init__(host, 'blog', use_profile=False, help=_('blog/microblog management'))
+        super(Blog, self).__init__(
+            host, "blog", use_profile=False, help=_("blog/microblog management")
+        )
--- a/sat_frontends/jp/cmd_debug.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/jp/cmd_debug.py	Wed Jun 27 20:14:46 2018 +0200
@@ -28,32 +28,35 @@
 
 
 class BridgeCommon(object):
-
     def evalArgs(self):
         if self.args.arg:
             try:
-                return eval(u'[{}]'.format(u",".join(self.args.arg)))
+                return eval(u"[{}]".format(u",".join(self.args.arg)))
             except SyntaxError as e:
-                self.disp(u"Can't evaluate arguments: {mess}\n{text}\n{offset}^".format(
-                    mess=e,
-                    text=e.text.decode('utf-8'),
-                    offset=u" "*(e.offset-1)
-                    ), error=True)
+                self.disp(
+                    u"Can't evaluate arguments: {mess}\n{text}\n{offset}^".format(
+                        mess=e, text=e.text.decode("utf-8"), offset=u" " * (e.offset - 1)
+                    ),
+                    error=True,
+                )
                 self.host.quit(C.EXIT_BAD_ARG)
         else:
             return []
 
 
 class Method(base.CommandBase, BridgeCommon):
-
     def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'method', help=_(u'call a bridge method'))
+        base.CommandBase.__init__(self, host, "method", help=_(u"call a bridge method"))
         BridgeCommon.__init__(self)
-        self.need_loop=True
+        self.need_loop = True
 
     def add_parser_options(self):
-        self.parser.add_argument("method", type=str, help=_(u"name of the method to execute"))
-        self.parser.add_argument("arg", type=base.unicode_decoder, nargs="*", help=_(u"argument of the method"))
+        self.parser.add_argument(
+            "method", type=str, help=_(u"name of the method to execute")
+        )
+        self.parser.add_argument(
+            "arg", type=base.unicode_decoder, nargs="*", help=_(u"argument of the method")
+        )
 
     def method_cb(self, ret=None):
         if ret is not None:
@@ -61,14 +64,22 @@
         self.host.quit()
 
     def method_eb(self, failure):
-        self.disp(_(u"Error while executing {}: {}".format(self.args.method, failure)), error=True)
+        self.disp(
+            _(u"Error while executing {}: {}".format(self.args.method, failure)),
+            error=True,
+        )
         self.host.quit(C.EXIT_ERROR)
 
     def start(self):
         method = getattr(self.host.bridge, self.args.method)
         args = self.evalArgs()
         try:
-            method(*args, profile=self.profile, callback=self.method_cb, errback=self.method_eb)
+            method(
+                *args,
+                profile=self.profile,
+                callback=self.method_cb,
+                errback=self.method_eb
+            )
         except TypeError:
             # maybe the method doesn't need a profile ?
             try:
@@ -78,14 +89,19 @@
 
 
 class Signal(base.CommandBase, BridgeCommon):
-
     def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'signal', help=_(u'send a fake signal from backend'))
+        base.CommandBase.__init__(
+            self, host, "signal", help=_(u"send a fake signal from backend")
+        )
         BridgeCommon.__init__(self)
 
     def add_parser_options(self):
-        self.parser.add_argument("signal", type=str, help=_(u"name of the signal to send"))
-        self.parser.add_argument("arg", type=base.unicode_decoder, nargs="*", help=_(u"argument of the signal"))
+        self.parser.add_argument(
+            "signal", type=str, help=_(u"name of the signal to send")
+        )
+        self.parser.add_argument(
+            "arg", type=base.unicode_decoder, nargs="*", help=_(u"argument of the signal")
+        )
 
     def start(self):
         args = self.evalArgs()
@@ -99,27 +115,36 @@
     subcommands = (Method, Signal)
 
     def __init__(self, host):
-        super(Bridge, self).__init__(host, 'bridge', use_profile=False, help=_('bridge s(t)imulation'))
+        super(Bridge, self).__init__(
+            host, "bridge", use_profile=False, help=_("bridge s(t)imulation")
+        )
 
 
 class Monitor(base.CommandBase):
-
     def __init__(self, host):
-        super(Monitor, self).__init__(host,
-                                      'monitor',
-                                      use_verbose=True,
-                                      use_profile=False,
-                                      use_output=C.OUTPUT_XML,
-                                      help=_('monitor XML stream'))
+        super(Monitor, self).__init__(
+            host,
+            "monitor",
+            use_verbose=True,
+            use_profile=False,
+            use_output=C.OUTPUT_XML,
+            help=_("monitor XML stream"),
+        )
         self.need_loop = True
 
     def add_parser_options(self):
-        self.parser.add_argument("-d", "--direction", choices=('in', 'out', 'both'), default='both', help=_(u"stream direction filter"))
+        self.parser.add_argument(
+            "-d",
+            "--direction",
+            choices=("in", "out", "both"),
+            default="both",
+            help=_(u"stream direction filter"),
+        )
 
     def printXML(self, direction, xml_data, profile):
-        if self.args.direction == 'in' and direction != 'IN':
+        if self.args.direction == "in" and direction != "IN":
             return
-        if self.args.direction == 'out' and direction != 'OUT':
+        if self.args.direction == "out" and direction != "OUT":
             return
         verbosity = self.host.verbosity
         if not xml_data.strip():
@@ -130,31 +155,41 @@
             whiteping = False
 
         if verbosity:
-            profile_disp = u' ({})'.format(profile) if verbosity>1 else u''
-            if direction == 'IN':
-                self.disp(A.color(A.BOLD, A.FG_YELLOW, '<<<===== IN ====', A.FG_WHITE, profile_disp))
+            profile_disp = u" ({})".format(profile) if verbosity > 1 else u""
+            if direction == "IN":
+                self.disp(
+                    A.color(
+                        A.BOLD, A.FG_YELLOW, "<<<===== IN ====", A.FG_WHITE, profile_disp
+                    )
+                )
             else:
-                self.disp(A.color(A.BOLD, A.FG_CYAN, '==== OUT ====>>>', A.FG_WHITE, profile_disp))
+                self.disp(
+                    A.color(
+                        A.BOLD, A.FG_CYAN, "==== OUT ====>>>", A.FG_WHITE, profile_disp
+                    )
+                )
         if whiteping:
-            self.disp('[WHITESPACE PING]')
+            self.disp("[WHITESPACE PING]")
         else:
             try:
                 self.output(xml_data)
             except Exception:
-                # initial stream is not valid XML,
+                #  initial stream is not valid XML,
                 # in this case we print directly to data
-                # FIXME: we should test directly lxml.etree.XMLSyntaxError
+                #  FIXME: we should test directly lxml.etree.XMLSyntaxError
                 #        but importing lxml directly here is not clean
                 #        should be wrapped in a custom Exception
                 self.disp(xml_data)
-                self.disp(u'')
+                self.disp(u"")
 
     def start(self):
-        self.host.bridge.register_signal('xmlLog', self.printXML, 'plugin')
+        self.host.bridge.register_signal("xmlLog", self.printXML, "plugin")
 
 
 class Debug(base.CommandBase):
     subcommands = (Bridge, Monitor)
 
     def __init__(self, host):
-        super(Debug, self).__init__(host, 'debug', use_profile=False, help=_('debugging tools'))
+        super(Debug, self).__init__(
+            host, "debug", use_profile=False, help=_("debugging tools")
+        )
--- a/sat_frontends/jp/cmd_event.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/jp/cmd_event.py	Wed Jun 27 20:14:46 2018 +0200
@@ -30,29 +30,31 @@
 
 __commands__ = ["Event"]
 
-OUTPUT_OPT_TABLE = u'table'
+OUTPUT_OPT_TABLE = u"table"
 
 # TODO: move date parsing to base, it may be useful for other commands
 
 
 class Get(base.CommandBase):
-
     def __init__(self, host):
-        base.CommandBase.__init__(self,
-                                  host,
-                                  'get',
-                                  use_output=C.OUTPUT_DICT,
-                                  use_pubsub=True, pubsub_flags={C.NODE, C.ITEM, C.SINGLE_ITEM},
-                                  use_verbose=True,
-                                  help=_(u'get event data'))
-        self.need_loop=True
+        base.CommandBase.__init__(
+            self,
+            host,
+            "get",
+            use_output=C.OUTPUT_DICT,
+            use_pubsub=True,
+            pubsub_flags={C.NODE, C.ITEM, C.SINGLE_ITEM},
+            use_verbose=True,
+            help=_(u"get event data"),
+        )
+        self.need_loop = True
 
     def add_parser_options(self):
         pass
 
     def eventInviteeGetCb(self, result):
         event_date, event_data = result
-        event_data['date'] = event_date
+        event_data["date"] = event_date
         self.output(event_data)
         self.host.quit()
 
@@ -63,18 +65,36 @@
             self.args.item,
             self.profile,
             callback=self.eventInviteeGetCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't get event data: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
+            errback=partial(
+                self.errback,
+                msg=_(u"can't get event data: {}"),
+                exit_code=C.EXIT_BRIDGE_ERRBACK,
+            ),
+        )
 
 
 class EventBase(object):
-
     def add_parser_options(self):
-        self.parser.add_argument("-i", "--id", type=base.unicode_decoder, default=u'', help=_(u"ID of the PubSub Item"))
-        self.parser.add_argument("-d", "--date", type=unicode, help=_(u"date of the event"))
-        self.parser.add_argument("-f", "--field", type=base.unicode_decoder, action='append', nargs=2, dest='fields',
-                                 metavar=(u"KEY", u"VALUE"), help=_(u"configuration field to set"))
+        self.parser.add_argument(
+            "-i",
+            "--id",
+            type=base.unicode_decoder,
+            default=u"",
+            help=_(u"ID of the PubSub Item"),
+        )
+        self.parser.add_argument(
+            "-d", "--date", type=unicode, help=_(u"date of the event")
+        )
+        self.parser.add_argument(
+            "-f",
+            "--field",
+            type=base.unicode_decoder,
+            action="append",
+            nargs=2,
+            dest="fields",
+            metavar=(u"KEY", u"VALUE"),
+            help=_(u"configuration field to set"),
+        )
 
     def parseFields(self):
         return dict(self.args.fields) if self.args.fields else {}
@@ -85,7 +105,9 @@
                 date = int(self.args.date)
             except ValueError:
                 try:
-                    date_time = du_parser.parse(self.args.date, dayfirst=not (u'-' in self.args.date))
+                    date_time = du_parser.parse(
+                        self.args.date, dayfirst=not (u"-" in self.args.date)
+                    )
                 except ValueError as e:
                     self.parser.error(_(u"Can't parse date: {msg}").format(msg=e))
                 if date_time.tzinfo is None:
@@ -99,12 +121,18 @@
 
 class Create(EventBase, base.CommandBase):
     def __init__(self, host):
-        super(Create, self).__init__(host, 'create', use_pubsub=True, pubsub_flags={C.NODE}, help=_('create or replace event'))
+        super(Create, self).__init__(
+            host,
+            "create",
+            use_pubsub=True,
+            pubsub_flags={C.NODE},
+            help=_("create or replace event"),
+        )
         EventBase.__init__(self)
-        self.need_loop=True
+        self.need_loop = True
 
     def eventCreateCb(self, node):
-        self.disp(_(u'Event created successfuly on node {node}').format(node=node))
+        self.disp(_(u"Event created successfuly on node {node}").format(node=node))
         self.host.quit()
 
     def start(self):
@@ -118,16 +146,25 @@
             self.args.id,
             self.profile,
             callback=self.eventCreateCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't create event: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
+            errback=partial(
+                self.errback,
+                msg=_(u"can't create event: {}"),
+                exit_code=C.EXIT_BRIDGE_ERRBACK,
+            ),
+        )
 
 
 class Modify(EventBase, base.CommandBase):
     def __init__(self, host):
-        super(Modify, self).__init__(host, 'modify', use_pubsub=True, pubsub_flags={C.NODE}, help=_('modify an existing event'))
+        super(Modify, self).__init__(
+            host,
+            "modify",
+            use_pubsub=True,
+            pubsub_flags={C.NODE},
+            help=_("modify an existing event"),
+        )
         EventBase.__init__(self)
-        self.need_loop=True
+        self.need_loop = True
 
     def start(self):
         fields = self.parseFields()
@@ -140,22 +177,27 @@
             fields,
             self.profile,
             callback=self.host.quit,
-            errback=partial(self.errback,
-                            msg=_(u"can't update event data: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
+            errback=partial(
+                self.errback,
+                msg=_(u"can't update event data: {}"),
+                exit_code=C.EXIT_BRIDGE_ERRBACK,
+            ),
+        )
 
 
 class InviteeGet(base.CommandBase):
-
     def __init__(self, host):
-        base.CommandBase.__init__(self,
-                                  host,
-                                  'get',
-                                  use_output=C.OUTPUT_DICT,
-                                  use_pubsub=True, pubsub_flags={C.NODE, C.ITEM, C.SINGLE_ITEM},
-                                  use_verbose=True,
-                                  help=_(u'get event attendance'))
-        self.need_loop=True
+        base.CommandBase.__init__(
+            self,
+            host,
+            "get",
+            use_output=C.OUTPUT_DICT,
+            use_pubsub=True,
+            pubsub_flags={C.NODE, C.ITEM, C.SINGLE_ITEM},
+            use_verbose=True,
+            help=_(u"get event attendance"),
+        )
+        self.need_loop = True
 
     def add_parser_options(self):
         pass
@@ -170,19 +212,37 @@
             self.args.node,
             self.profile,
             callback=self.eventInviteeGetCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't get event data: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
+            errback=partial(
+                self.errback,
+                msg=_(u"can't get event data: {}"),
+                exit_code=C.EXIT_BRIDGE_ERRBACK,
+            ),
+        )
 
 
 class InviteeSet(base.CommandBase):
     def __init__(self, host):
-        super(InviteeSet, self).__init__(host, 'set', use_output=C.OUTPUT_DICT, use_pubsub=True, pubsub_flags={C.NODE}, help=_('set event attendance'))
-        self.need_loop=True
+        super(InviteeSet, self).__init__(
+            host,
+            "set",
+            use_output=C.OUTPUT_DICT,
+            use_pubsub=True,
+            pubsub_flags={C.NODE},
+            help=_("set event attendance"),
+        )
+        self.need_loop = True
 
     def add_parser_options(self):
-        self.parser.add_argument("-f", "--field", type=base.unicode_decoder, action='append', nargs=2, dest='fields',
-                                 metavar=(u"KEY", u"VALUE"), help=_(u"configuration field to set"))
+        self.parser.add_argument(
+            "-f",
+            "--field",
+            type=base.unicode_decoder,
+            action="append",
+            nargs=2,
+            dest="fields",
+            metavar=(u"KEY", u"VALUE"),
+            help=_(u"configuration field to set"),
+        )
 
     def start(self):
         fields = dict(self.args.fields) if self.args.fields else {}
@@ -192,40 +252,55 @@
             fields,
             self.profile,
             callback=self.host.quit,
-            errback=partial(self.errback,
-                            msg=_(u"can't set event data: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
+            errback=partial(
+                self.errback,
+                msg=_(u"can't set event data: {}"),
+                exit_code=C.EXIT_BRIDGE_ERRBACK,
+            ),
+        )
 
 
 class InviteesList(base.CommandBase):
-
     def __init__(self, host):
-        extra_outputs = {'default': self.default_output}
-        base.CommandBase.__init__(self,
-                                  host,
-                                  'list',
-                                  use_output=C.OUTPUT_DICT_DICT,
-                                  extra_outputs=extra_outputs,
-                                  use_pubsub=True, pubsub_flags={C.NODE},
-                                  use_verbose=True,
-                                  help=_(u'get event attendance'))
-        self.need_loop=True
+        extra_outputs = {"default": self.default_output}
+        base.CommandBase.__init__(
+            self,
+            host,
+            "list",
+            use_output=C.OUTPUT_DICT_DICT,
+            extra_outputs=extra_outputs,
+            use_pubsub=True,
+            pubsub_flags={C.NODE},
+            use_verbose=True,
+            help=_(u"get event attendance"),
+        )
+        self.need_loop = True
 
     def add_parser_options(self):
-        self.parser.add_argument('-m', '--missing', action='store_true', help=_(u'show missing people (invited but no R.S.V.P. so far)'))
-        self.parser.add_argument('-R', '--no-rsvp', action='store_true', help=_(u"don't show people which gave R.S.V.P."))
+        self.parser.add_argument(
+            "-m",
+            "--missing",
+            action="store_true",
+            help=_(u"show missing people (invited but no R.S.V.P. so far)"),
+        )
+        self.parser.add_argument(
+            "-R",
+            "--no-rsvp",
+            action="store_true",
+            help=_(u"don't show people which gave R.S.V.P."),
+        )
 
     def _attend_filter(self, attend, row):
-        if attend == u'yes':
+        if attend == u"yes":
             attend_color = C.A_SUCCESS
-        elif attend == u'no':
+        elif attend == u"no":
             attend_color = C.A_FAILURE
         else:
             attend_color = A.FG_WHITE
         return A.color(attend_color, attend)
 
     def _guests_filter(self, guests):
-        return u'(' + unicode(guests) + ')' if guests else u''
+        return u"(" + unicode(guests) + ")" if guests else u""
 
     def default_output(self, event_data):
         data = []
@@ -236,74 +311,97 @@
         guests = 0
         guests_maybe = 0
         for jid_, jid_data in event_data.iteritems():
-            jid_data[u'jid'] = jid_
+            jid_data[u"jid"] = jid_
             try:
-                guests_int = int(jid_data['guests'])
+                guests_int = int(jid_data["guests"])
             except (ValueError, KeyError):
                 pass
-            attend = jid_data.get(u'attend',u'')
-            if attend == 'yes':
+            attend = jid_data.get(u"attend", u"")
+            if attend == "yes":
                 attendees_yes += 1
                 guests += guests_int
-            elif attend == 'maybe':
+            elif attend == "maybe":
                 attendees_maybe += 1
                 guests_maybe += guests_int
-            elif attend == 'no':
+            elif attend == "no":
                 attendees_no += 1
-                jid_data[u'guests'] = ''
+                jid_data[u"guests"] = ""
             else:
                 attendees_missing += 1
-                jid_data[u'guests'] = ''
+                jid_data[u"guests"] = ""
             data.append(jid_data)
 
         show_table = OUTPUT_OPT_TABLE in self.args.output_opts
 
-        table = common.Table.fromDict(self.host,
+        table = common.Table.fromDict(
+            self.host,
             data,
-            (u'nick',) + ((u'jid',) if self.host.verbosity else ()) +  (u'attend', 'guests'),
+            (u"nick",)
+            + ((u"jid",) if self.host.verbosity else ())
+            + (u"attend", "guests"),
             headers=None,
-            filters = { u'nick': A.color(C.A_HEADER, u'{}' if show_table else u'{} '),
-                        u'jid': u'{}' if show_table else u'{} ',
-                        u'attend': self._attend_filter,
-                        u'guests': u'{}' if show_table else self._guests_filter,
-                      },
-            defaults = { u'nick': u'',
-                         u'attend': u'',
-                         u'guests': 1
-                       }
-            )
+            filters={
+                u"nick": A.color(C.A_HEADER, u"{}" if show_table else u"{} "),
+                u"jid": u"{}" if show_table else u"{} ",
+                u"attend": self._attend_filter,
+                u"guests": u"{}" if show_table else self._guests_filter,
+            },
+            defaults={u"nick": u"", u"attend": u"", u"guests": 1},
+        )
         if show_table:
             table.display()
         else:
-            table.display_blank(show_header=False, col_sep=u'')
+            table.display_blank(show_header=False, col_sep=u"")
 
         if not self.args.no_rsvp:
-            self.disp(u'')
-            self.disp(A.color(
-                C.A_SUBHEADER,
-                _(u'Attendees: '),
-                A.RESET,
-                unicode(len(data)),
-                _(u' ('),
-                C.A_SUCCESS,
-                _(u'yes: '),
-                unicode(attendees_yes),
-                A.FG_WHITE,
-                _(u', maybe: '),
-                unicode(attendees_maybe),
-                u', ',
-                C.A_FAILURE,
-                _(u'no: '),
-                unicode(attendees_no),
-                A.RESET,
-                u')'
-                ))
-            self.disp(A.color(C.A_SUBHEADER, _(u'confirmed guests: '), A.RESET, unicode(guests)))
-            self.disp(A.color(C.A_SUBHEADER, _(u'unconfirmed guests: '), A.RESET, unicode(guests_maybe)))
-            self.disp(A.color(C.A_SUBHEADER, _(u'total: '), A.RESET, unicode(guests+guests_maybe)))
+            self.disp(u"")
+            self.disp(
+                A.color(
+                    C.A_SUBHEADER,
+                    _(u"Attendees: "),
+                    A.RESET,
+                    unicode(len(data)),
+                    _(u" ("),
+                    C.A_SUCCESS,
+                    _(u"yes: "),
+                    unicode(attendees_yes),
+                    A.FG_WHITE,
+                    _(u", maybe: "),
+                    unicode(attendees_maybe),
+                    u", ",
+                    C.A_FAILURE,
+                    _(u"no: "),
+                    unicode(attendees_no),
+                    A.RESET,
+                    u")",
+                )
+            )
+            self.disp(
+                A.color(C.A_SUBHEADER, _(u"confirmed guests: "), A.RESET, unicode(guests))
+            )
+            self.disp(
+                A.color(
+                    C.A_SUBHEADER,
+                    _(u"unconfirmed guests: "),
+                    A.RESET,
+                    unicode(guests_maybe),
+                )
+            )
+            self.disp(
+                A.color(
+                    C.A_SUBHEADER, _(u"total: "), A.RESET, unicode(guests + guests_maybe)
+                )
+            )
         if attendees_missing:
-            self.disp('')
-            self.disp(A.color(C.A_SUBHEADER, _(u'missing people (no reply): '), A.RESET, unicode(attendees_missing)))
+            self.disp("")
+            self.disp(
+                A.color(
+                    C.A_SUBHEADER,
+                    _(u"missing people (no reply): "),
+                    A.RESET,
+                    unicode(attendees_missing),
+                )
+            )
 
     def eventInviteesListCb(self, event_data, prefilled_data):
         """fill nicknames and keep only requested people
@@ -325,7 +423,7 @@
         # we get nicknames for everybody, make it easier for organisers
         for jid_, data in prefilled_data.iteritems():
             id_data = self.host.bridge.identityGet(jid_, self.profile)
-            data[u'nick'] = id_data.get(u'nick', u'')
+            data[u"nick"] = id_data.get(u"nick", u"")
 
         self.output(prefilled_data)
         self.host.quit()
@@ -335,17 +433,23 @@
             self.args.service,
             self.args.node,
             self.profile,
-            callback=partial(self.eventInviteesListCb,
-                             prefilled_data=prefilled_data),
-            errback=partial(self.errback,
-                            msg=_(u"can't get event data: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
+            callback=partial(self.eventInviteesListCb, prefilled_data=prefilled_data),
+            errback=partial(
+                self.errback,
+                msg=_(u"can't get event data: {}"),
+                exit_code=C.EXIT_BRIDGE_ERRBACK,
+            ),
+        )
 
     def psNodeAffiliationsGetCb(self, affiliations):
         # we fill all affiliations with empty data
         # answered one will be filled in eventInviteesListCb
         # we only consider people with "publisher" affiliation as invited, creators are not, and members can just observe
-        prefilled = {jid_: {} for jid_, affiliation in affiliations.iteritems() if affiliation in (u'publisher',)}
+        prefilled = {
+            jid_: {}
+            for jid_, affiliation in affiliations.iteritems()
+            if affiliation in (u"publisher",)
+        }
         self.getList(prefilled)
 
     def start(self):
@@ -357,27 +461,79 @@
                 self.args.node,
                 self.profile,
                 callback=self.psNodeAffiliationsGetCb,
-                errback=partial(self.errback,
-                                msg=_(u"can't get event data: {}"),
-                                exit_code=C.EXIT_BRIDGE_ERRBACK))
+                errback=partial(
+                    self.errback,
+                    msg=_(u"can't get event data: {}"),
+                    exit_code=C.EXIT_BRIDGE_ERRBACK,
+                ),
+            )
         else:
             self.getList()
 
 
 class InviteeInvite(base.CommandBase):
-
     def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'invite', use_pubsub=True, pubsub_flags={C.NODE, C.SINGLE_ITEM}, help=_(u'invite someone to the event through email'))
-        self.need_loop=True
+        base.CommandBase.__init__(
+            self,
+            host,
+            "invite",
+            use_pubsub=True,
+            pubsub_flags={C.NODE, C.SINGLE_ITEM},
+            help=_(u"invite someone to the event through email"),
+        )
+        self.need_loop = True
 
     def add_parser_options(self):
-        self.parser.add_argument("-e", "--email", action="append", type=base.unicode_decoder, default=[], help='email(s) to send the invitation to')
-        self.parser.add_argument("-N", "--name", type=base.unicode_decoder, default='', help='name of the invitee')
-        self.parser.add_argument("-H", "--host-name", type=base.unicode_decoder, default='', help='name of the host')
-        self.parser.add_argument("-l", "--lang", type=base.unicode_decoder, default='', help='main language spoken by the invitee')
-        self.parser.add_argument("-U", "--url-template", type=base.unicode_decoder, default='', help='template to construct the URL')
-        self.parser.add_argument("-S", "--subject", type=base.unicode_decoder, default='', help='subject of the invitation email (default: generic subject)')
-        self.parser.add_argument("-b", "--body", type=base.unicode_decoder, default='', help='body of the invitation email (default: generic body)')
+        self.parser.add_argument(
+            "-e",
+            "--email",
+            action="append",
+            type=base.unicode_decoder,
+            default=[],
+            help="email(s) to send the invitation to",
+        )
+        self.parser.add_argument(
+            "-N",
+            "--name",
+            type=base.unicode_decoder,
+            default="",
+            help="name of the invitee",
+        )
+        self.parser.add_argument(
+            "-H",
+            "--host-name",
+            type=base.unicode_decoder,
+            default="",
+            help="name of the host",
+        )
+        self.parser.add_argument(
+            "-l",
+            "--lang",
+            type=base.unicode_decoder,
+            default="",
+            help="main language spoken by the invitee",
+        )
+        self.parser.add_argument(
+            "-U",
+            "--url-template",
+            type=base.unicode_decoder,
+            default="",
+            help="template to construct the URL",
+        )
+        self.parser.add_argument(
+            "-S",
+            "--subject",
+            type=base.unicode_decoder,
+            default="",
+            help="subject of the invitation email (default: generic subject)",
+        )
+        self.parser.add_argument(
+            "-b",
+            "--body",
+            type=base.unicode_decoder,
+            default="",
+            help="body of the invitation email (default: generic body)",
+        )
 
     def start(self):
         email = self.args.email[0] if self.args.email else None
@@ -397,20 +553,27 @@
             self.args.body,
             self.args.profile,
             callback=self.host.quit,
-            errback=partial(self.errback,
-                            msg=_(u"can't create invitation: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
+            errback=partial(
+                self.errback,
+                msg=_(u"can't create invitation: {}"),
+                exit_code=C.EXIT_BRIDGE_ERRBACK,
+            ),
+        )
 
 
 class Invitee(base.CommandBase):
     subcommands = (InviteeGet, InviteeSet, InviteesList, InviteeInvite)
 
     def __init__(self, host):
-        super(Invitee, self).__init__(host, 'invitee', use_profile=False, help=_(u'manage invities'))
+        super(Invitee, self).__init__(
+            host, "invitee", use_profile=False, help=_(u"manage invities")
+        )
 
 
 class Event(base.CommandBase):
     subcommands = (Get, Create, Modify, Invitee)
 
     def __init__(self, host):
-        super(Event, self).__init__(host, 'event', use_profile=False, help=_('event management'))
+        super(Event, self).__init__(
+            host, "event", use_profile=False, help=_("event management")
+        )
--- a/sat_frontends/jp/cmd_file.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/jp/cmd_file.py	Wed Jun 27 20:14:46 2018 +0200
@@ -29,7 +29,7 @@
 from sat_frontends.tools import jid
 from sat.tools.common.ansi import ANSI as A
 import tempfile
-import xml.etree.ElementTree as ET # FIXME: used temporarily to manage XMLUI
+import xml.etree.ElementTree as ET  # FIXME: used temporarily to manage XMLUI
 from functools import partial
 import json
 
@@ -38,32 +38,60 @@
 
 class Send(base.CommandBase):
     def __init__(self, host):
-        super(Send, self).__init__(host, 'send', use_progress=True, use_verbose=True, help=_('send a file to a contact'))
-        self.need_loop=True
+        super(Send, self).__init__(
+            host,
+            "send",
+            use_progress=True,
+            use_verbose=True,
+            help=_("send a file to a contact"),
+        )
+        self.need_loop = True
 
     def add_parser_options(self):
-        self.parser.add_argument("files", type=str, nargs='+', metavar='file', help=_(u"a list of file"))
-        self.parser.add_argument("jid", type=base.unicode_decoder, help=_(u"the destination jid"))
-        self.parser.add_argument("-b", "--bz2", action="store_true", help=_(u"make a bzip2 tarball"))
-        self.parser.add_argument("-d", "--path", type=base.unicode_decoder, help=(u"path to the directory where the file must be stored"))
-        self.parser.add_argument("-N", "--namespace", type=base.unicode_decoder, help=(u"namespace of the file"))
-        self.parser.add_argument("-n", "--name", type=base.unicode_decoder, default=u'', help=(u"name to use (DEFAULT: use source file name)"))
+        self.parser.add_argument(
+            "files", type=str, nargs="+", metavar="file", help=_(u"a list of file")
+        )
+        self.parser.add_argument(
+            "jid", type=base.unicode_decoder, help=_(u"the destination jid")
+        )
+        self.parser.add_argument(
+            "-b", "--bz2", action="store_true", help=_(u"make a bzip2 tarball")
+        )
+        self.parser.add_argument(
+            "-d",
+            "--path",
+            type=base.unicode_decoder,
+            help=(u"path to the directory where the file must be stored"),
+        )
+        self.parser.add_argument(
+            "-N",
+            "--namespace",
+            type=base.unicode_decoder,
+            help=(u"namespace of the file"),
+        )
+        self.parser.add_argument(
+            "-n",
+            "--name",
+            type=base.unicode_decoder,
+            default=u"",
+            help=(u"name to use (DEFAULT: use source file name)"),
+        )
 
     def start(self):
         """Send files to jabber contact"""
         self.send_files()
 
     def onProgressStarted(self, metadata):
-        self.disp(_(u'File copy started'),2)
+        self.disp(_(u"File copy started"), 2)
 
     def onProgressFinished(self, metadata):
-        self.disp(_(u'File sent successfully'),2)
+        self.disp(_(u"File sent successfully"), 2)
 
     def onProgressError(self, error_msg):
         if error_msg == C.PROGRESS_ERROR_DECLINED:
-            self.disp(_(u'The file has been refused by your contact'))
+            self.disp(_(u"The file has been refused by your contact"))
         else:
-            self.disp(_(u'Error while sending file: {}').format(error_msg),error=True)
+            self.disp(_(u"Error while sending file: {}").format(error_msg), error=True)
 
     def gotId(self, data, file_):
         """Called when a progress id has been received
@@ -71,17 +99,22 @@
         @param pid(unicode): progress id
         @param file_(str): file path
         """
-        #FIXME: this show progress only for last progress_id
+        # FIXME: this show progress only for last progress_id
         self.disp(_(u"File request sent to {jid}".format(jid=self.full_dest_jid)), 1)
         try:
-            self.progress_id = data['progress']
+            self.progress_id = data["progress"]
         except KeyError:
             # TODO: if 'xmlui' key is present, manage xmlui message display
-            self.disp(_(u"Can't send file to {jid}".format(jid=self.full_dest_jid)), error=True)
+            self.disp(
+                _(u"Can't send file to {jid}".format(jid=self.full_dest_jid)), error=True
+            )
             self.host.quit(2)
 
     def error(self, failure):
-        self.disp(_("Error while trying to send a file: {reason}").format(reason=failure), error=True)
+        self.disp(
+            _("Error while trying to send a file: {reason}").format(reason=failure),
+            error=True,
+        )
         self.host.quit(1)
 
     def send_files(self):
@@ -90,71 +123,140 @@
                 self.disp(_(u"file [{}] doesn't exist !").format(file_), error=True)
                 self.host.quit(1)
             if not self.args.bz2 and os.path.isdir(file_):
-                self.disp(_(u"[{}] is a dir ! Please send files inside or use compression").format(file_))
+                self.disp(
+                    _(
+                        u"[{}] is a dir ! Please send files inside or use compression"
+                    ).format(file_)
+                )
                 self.host.quit(1)
 
         self.full_dest_jid = self.host.get_full_jid(self.args.jid)
         extra = {}
         if self.args.path:
-            extra[u'path'] = self.args.path
+            extra[u"path"] = self.args.path
         if self.args.namespace:
-            extra[u'namespace'] = self.args.namespace
+            extra[u"namespace"] = self.args.namespace
 
         if self.args.bz2:
-            with tempfile.NamedTemporaryFile('wb', delete=False) as buf:
+            with tempfile.NamedTemporaryFile("wb", delete=False) as buf:
                 self.host.addOnQuitCallback(os.unlink, buf.name)
                 self.disp(_(u"bz2 is an experimental option, use with caution"))
-                #FIXME: check free space
+                # FIXME: check free space
                 self.disp(_(u"Starting compression, please wait..."))
                 sys.stdout.flush()
                 bz2 = tarfile.open(mode="w:bz2", fileobj=buf)
-                archive_name = u'{}.tar.bz2'.format(os.path.basename(self.args.files[0]) or u'compressed_files')
+                archive_name = u"{}.tar.bz2".format(
+                    os.path.basename(self.args.files[0]) or u"compressed_files"
+                )
                 for file_ in self.args.files:
                     self.disp(_(u"Adding {}").format(file_), 1)
                     bz2.add(file_)
                 bz2.close()
                 self.disp(_(u"Done !"), 1)
 
-                self.host.bridge.fileSend(self.full_dest_jid, buf.name, self.args.name or archive_name, '', extra, self.profile,
-                                          callback=lambda pid, file_=buf.name: self.gotId(pid, file_), errback=self.error)
+                self.host.bridge.fileSend(
+                    self.full_dest_jid,
+                    buf.name,
+                    self.args.name or archive_name,
+                    "",
+                    extra,
+                    self.profile,
+                    callback=lambda pid, file_=buf.name: self.gotId(pid, file_),
+                    errback=self.error,
+                )
         else:
             for file_ in self.args.files:
                 path = os.path.abspath(file_)
-                self.host.bridge.fileSend(self.full_dest_jid, path, self.args.name, '', extra, self.profile,
-                                          callback=lambda pid, file_=file_: self.gotId(pid, file_), errback=self.error)
+                self.host.bridge.fileSend(
+                    self.full_dest_jid,
+                    path,
+                    self.args.name,
+                    "",
+                    extra,
+                    self.profile,
+                    callback=lambda pid, file_=file_: self.gotId(pid, file_),
+                    errback=self.error,
+                )
 
 
 class Request(base.CommandBase):
-
     def __init__(self, host):
-        super(Request, self).__init__(host, 'request', use_progress=True, use_verbose=True, help=_('request a file from a contact'))
-        self.need_loop=True
+        super(Request, self).__init__(
+            host,
+            "request",
+            use_progress=True,
+            use_verbose=True,
+            help=_("request a file from a contact"),
+        )
+        self.need_loop = True
 
     @property
     def filename(self):
         return self.args.name or self.args.hash or u"output"
 
     def add_parser_options(self):
-        self.parser.add_argument("jid", type=base.unicode_decoder, help=_(u"the destination jid"))
-        self.parser.add_argument("-D", "--dest", type=base.unicode_decoder, help=_(u"destination path where the file will be saved (default: [current_dir]/[name|hash])"))
-        self.parser.add_argument("-n", "--name", type=base.unicode_decoder, default=u'', help=_(u"name of the file"))
-        self.parser.add_argument("-H", "--hash", type=base.unicode_decoder, default=u'', help=_(u"hash of the file"))
-        self.parser.add_argument("-a", "--hash-algo", type=base.unicode_decoder, default=u'sha-256', help=_(u"hash algorithm use for --hash (default: sha-256)"))
-        self.parser.add_argument("-d", "--path", type=base.unicode_decoder, help=(u"path to the directory containing the file"))
-        self.parser.add_argument("-N", "--namespace", type=base.unicode_decoder, help=(u"namespace of the file"))
-        self.parser.add_argument("-f", "--force", action='store_true', help=_(u"overwrite existing file without confirmation"))
+        self.parser.add_argument(
+            "jid", type=base.unicode_decoder, help=_(u"the destination jid")
+        )
+        self.parser.add_argument(
+            "-D",
+            "--dest",
+            type=base.unicode_decoder,
+            help=_(
+                u"destination path where the file will be saved (default: [current_dir]/[name|hash])"
+            ),
+        )
+        self.parser.add_argument(
+            "-n",
+            "--name",
+            type=base.unicode_decoder,
+            default=u"",
+            help=_(u"name of the file"),
+        )
+        self.parser.add_argument(
+            "-H",
+            "--hash",
+            type=base.unicode_decoder,
+            default=u"",
+            help=_(u"hash of the file"),
+        )
+        self.parser.add_argument(
+            "-a",
+            "--hash-algo",
+            type=base.unicode_decoder,
+            default=u"sha-256",
+            help=_(u"hash algorithm use for --hash (default: sha-256)"),
+        )
+        self.parser.add_argument(
+            "-d",
+            "--path",
+            type=base.unicode_decoder,
+            help=(u"path to the directory containing the file"),
+        )
+        self.parser.add_argument(
+            "-N",
+            "--namespace",
+            type=base.unicode_decoder,
+            help=(u"namespace of the file"),
+        )
+        self.parser.add_argument(
+            "-f",
+            "--force",
+            action="store_true",
+            help=_(u"overwrite existing file without confirmation"),
+        )
 
     def onProgressStarted(self, metadata):
-        self.disp(_(u'File copy started'),2)
+        self.disp(_(u"File copy started"), 2)
 
     def onProgressFinished(self, metadata):
-        self.disp(_(u'File received successfully'),2)
+        self.disp(_(u"File received successfully"), 2)
 
     def onProgressError(self, error_msg):
         if error_msg == C.PROGRESS_ERROR_DECLINED:
-            self.disp(_(u'The file request has been refused'))
+            self.disp(_(u"The file request has been refused"))
         else:
-            self.disp(_(u'Error while requesting file: {}').format(error_msg), error=True)
+            self.disp(_(u"Error while requesting file: {}").format(error_msg), error=True)
 
     def gotId(self, progress_id):
         """Called when a progress id has been received
@@ -164,13 +266,16 @@
         self.progress_id = progress_id
 
     def error(self, failure):
-        self.disp(_("Error while trying to send a file: {reason}").format(reason=failure), error=True)
+        self.disp(
+            _("Error while trying to send a file: {reason}").format(reason=failure),
+            error=True,
+        )
         self.host.quit(1)
 
     def start(self):
         if not self.args.name and not self.args.hash:
-            self.parser.error(_(u'at least one of --name or --hash must be provided'))
-        # extra = dict(self.args.extra)
+            self.parser.error(_(u"at least one of --name or --hash must be provided"))
+        #  extra = dict(self.args.extra)
         if self.args.dest:
             path = os.path.abspath(os.path.expanduser(self.args.dest))
             if os.path.isdir(path):
@@ -179,8 +284,10 @@
             path = os.path.abspath(self.filename)
 
         if os.path.exists(path) and not self.args.force:
-            message = _(u'File {path} already exists! Do you want to overwrite?').format(path=path)
-            confirm = raw_input(u"{} (y/N) ".format(message).encode('utf-8'))
+            message = _(u"File {path} already exists! Do you want to overwrite?").format(
+                path=path
+            )
+            confirm = raw_input(u"{} (y/N) ".format(message).encode("utf-8"))
             if confirm not in (u"y", u"Y"):
                 self.disp(_(u"file request cancelled"))
                 self.host.quit(2)
@@ -188,60 +295,73 @@
         self.full_dest_jid = self.host.get_full_jid(self.args.jid)
         extra = {}
         if self.args.path:
-            extra[u'path'] = self.args.path
+            extra[u"path"] = self.args.path
         if self.args.namespace:
-            extra[u'namespace'] = self.args.namespace
-        self.host.bridge.fileJingleRequest(self.full_dest_jid,
-                                           path,
-                                           self.args.name,
-                                           self.args.hash,
-                                           self.args.hash_algo if self.args.hash else u'',
-                                           extra,
-                                           self.profile,
-                                           callback=self.gotId,
-                                           errback=partial(self.errback,
-                                               msg=_(u"can't request file: {}"),
-                                               exit_code=C.EXIT_BRIDGE_ERRBACK))
+            extra[u"namespace"] = self.args.namespace
+        self.host.bridge.fileJingleRequest(
+            self.full_dest_jid,
+            path,
+            self.args.name,
+            self.args.hash,
+            self.args.hash_algo if self.args.hash else u"",
+            extra,
+            self.profile,
+            callback=self.gotId,
+            errback=partial(
+                self.errback,
+                msg=_(u"can't request file: {}"),
+                exit_code=C.EXIT_BRIDGE_ERRBACK,
+            ),
+        )
 
 
 class Receive(base.CommandAnswering):
-
     def __init__(self, host):
-        super(Receive, self).__init__(host, 'receive', use_progress=True, use_verbose=True, help=_('wait for a file to be sent by a contact'))
-        self._overwrite_refused = False # True when one overwrite as already been refused
-        self.action_callbacks = {C.META_TYPE_FILE: self.onFileAction,
-                                 C.META_TYPE_OVERWRITE: self.onOverwriteAction}
+        super(Receive, self).__init__(
+            host,
+            "receive",
+            use_progress=True,
+            use_verbose=True,
+            help=_("wait for a file to be sent by a contact"),
+        )
+        self._overwrite_refused = False  # True when one overwrite as already been refused
+        self.action_callbacks = {
+            C.META_TYPE_FILE: self.onFileAction,
+            C.META_TYPE_OVERWRITE: self.onOverwriteAction,
+        }
 
     def onProgressStarted(self, metadata):
-        self.disp(_(u'File copy started'),2)
+        self.disp(_(u"File copy started"), 2)
 
     def onProgressFinished(self, metadata):
-        self.disp(_(u'File received successfully'),2)
-        if metadata.get('hash_verified', False):
+        self.disp(_(u"File received successfully"), 2)
+        if metadata.get("hash_verified", False):
             try:
-                self.disp(_(u'hash checked: {algo}:{checksum}').format(
-                    algo=metadata['hash_algo'],
-                    checksum=metadata['hash']),
-                    1)
+                self.disp(
+                    _(u"hash checked: {algo}:{checksum}").format(
+                        algo=metadata["hash_algo"], checksum=metadata["hash"]
+                    ),
+                    1,
+                )
             except KeyError:
-                self.disp(_(u'hash is checked but hash value is missing', 1), error=True)
+                self.disp(_(u"hash is checked but hash value is missing", 1), error=True)
         else:
             self.disp(_(u"hash can't be verified"), 1)
 
     def onProgressError(self, error_msg):
-        self.disp(_(u'Error while receiving file: {}').format(error_msg),error=True)
+        self.disp(_(u"Error while receiving file: {}").format(error_msg), error=True)
 
     def getXmluiId(self, action_data):
         # FIXME: we temporarily use ElementTree, but a real XMLUI managing module
         #        should be available in the futur
         # TODO: XMLUI module
         try:
-            xml_ui = action_data['xmlui']
+            xml_ui = action_data["xmlui"]
         except KeyError:
             self.disp(_(u"Action has no XMLUI"), 1)
         else:
-            ui = ET.fromstring(xml_ui.encode('utf-8'))
-            xmlui_id = ui.get('submit')
+            ui = ET.fromstring(xml_ui.encode("utf-8"))
+            xmlui_id = ui.get("submit")
             if not xmlui_id:
                 self.disp(_(u"Invalid XMLUI received"), error=True)
             return xmlui_id
@@ -251,12 +371,12 @@
         if xmlui_id is None:
             return self.host.quitFromSignal(1)
         try:
-            from_jid = jid.JID(action_data['meta_from_jid'])
+            from_jid = jid.JID(action_data["meta_from_jid"])
         except KeyError:
             self.disp(_(u"Ignoring action without from_jid data"), 1)
             return
         try:
-            progress_id = action_data['meta_progress_id']
+            progress_id = action_data["meta_progress_id"]
         except KeyError:
             self.disp(_(u"ignoring action without progress id"), 1)
             return
@@ -264,10 +384,12 @@
         if not self.bare_jids or from_jid.bare in self.bare_jids:
             if self._overwrite_refused:
                 self.disp(_(u"File refused because overwrite is needed"), error=True)
-                self.host.bridge.launchAction(xmlui_id, {'cancelled': C.BOOL_TRUE}, profile_key=profile)
+                self.host.bridge.launchAction(
+                    xmlui_id, {"cancelled": C.BOOL_TRUE}, profile_key=profile
+                )
                 return self.host.quitFromSignal(2)
             self.progress_id = progress_id
-            xmlui_data = {'path': self.path}
+            xmlui_data = {"path": self.path}
             self.host.bridge.launchAction(xmlui_id, xmlui_data, profile_key=profile)
 
     def onOverwriteAction(self, action_data, action_id, security_limit, profile):
@@ -275,7 +397,7 @@
         if xmlui_id is None:
             return self.host.quitFromSignal(1)
         try:
-            progress_id = action_data['meta_progress_id']
+            progress_id = action_data["meta_progress_id"]
         except KeyError:
             self.disp(_(u"ignoring action without progress id"), 1)
             return
@@ -288,14 +410,36 @@
                 self.disp(_(u"Refused to overwrite"), 2)
                 self._overwrite_refused = True
 
-            xmlui_data = {'answer': C.boolConst(self.args.force)}
+            xmlui_data = {"answer": C.boolConst(self.args.force)}
             self.host.bridge.launchAction(xmlui_id, xmlui_data, profile_key=profile)
 
     def add_parser_options(self):
-        self.parser.add_argument("jids", type=base.unicode_decoder, nargs="*", help=_(u'jids accepted (accept everything if none is specified)'))
-        self.parser.add_argument("-m", "--multiple", action="store_true", help=_(u"accept multiple files (you'll have to stop manually)"))
-        self.parser.add_argument("-f", "--force", action="store_true", help=_(u"force overwritting of existing files (/!\\ name is choosed by sender)"))
-        self.parser.add_argument("--path", default='.', metavar='DIR', help=_(u"destination path (default: working directory)"))
+        self.parser.add_argument(
+            "jids",
+            type=base.unicode_decoder,
+            nargs="*",
+            help=_(u"jids accepted (accept everything if none is specified)"),
+        )
+        self.parser.add_argument(
+            "-m",
+            "--multiple",
+            action="store_true",
+            help=_(u"accept multiple files (you'll have to stop manually)"),
+        )
+        self.parser.add_argument(
+            "-f",
+            "--force",
+            action="store_true",
+            help=_(
+                u"force overwritting of existing files (/!\\ name is choosed by sender)"
+            ),
+        )
+        self.parser.add_argument(
+            "--path",
+            default=".",
+            metavar="DIR",
+            help=_(u"destination path (default: working directory)"),
+        )
 
     def start(self):
         self.bare_jids = [jid.JID(jid_).bare for jid_ in self.args.jids]
@@ -305,36 +449,46 @@
             self.host.quit(2)
         if self.args.multiple:
             self.host.quit_on_progress_end = False
-        self.disp(_(u"waiting for incoming file request"),2)
+        self.disp(_(u"waiting for incoming file request"), 2)
 
 
 class Upload(base.CommandBase):
-
     def __init__(self, host):
-        super(Upload, self).__init__(host, 'upload', use_progress=True, use_verbose=True, help=_('upload a file'))
-        self.need_loop=True
+        super(Upload, self).__init__(
+            host, "upload", use_progress=True, use_verbose=True, help=_("upload a file")
+        )
+        self.need_loop = True
 
     def add_parser_options(self):
         self.parser.add_argument("file", type=str, help=_("file to upload"))
-        self.parser.add_argument("jid", type=base.unicode_decoder, nargs='?',  help=_("jid of upload component (nothing to autodetect)"))
-        self.parser.add_argument("--ignore-tls-errors", action="store_true", help=_("ignore invalide TLS certificate"))
+        self.parser.add_argument(
+            "jid",
+            type=base.unicode_decoder,
+            nargs="?",
+            help=_("jid of upload component (nothing to autodetect)"),
+        )
+        self.parser.add_argument(
+            "--ignore-tls-errors",
+            action="store_true",
+            help=_("ignore invalide TLS certificate"),
+        )
 
     def onProgressStarted(self, metadata):
-        self.disp(_(u'File upload started'),2)
+        self.disp(_(u"File upload started"), 2)
 
     def onProgressFinished(self, metadata):
-        self.disp(_(u'File uploaded successfully'),2)
+        self.disp(_(u"File uploaded successfully"), 2)
         try:
-            url = metadata['url']
+            url = metadata["url"]
         except KeyError:
-            self.disp(u'download URL not found in metadata')
+            self.disp(u"download URL not found in metadata")
         else:
-            self.disp(_(u'URL to retrieve the file:'),1)
+            self.disp(_(u"URL to retrieve the file:"), 1)
             # XXX: url is display alone on a line to make parsing easier
             self.disp(url)
 
     def onProgressError(self, error_msg):
-        self.disp(_(u'Error while uploading file: {}').format(error_msg),error=True)
+        self.disp(_(u"Error while uploading file: {}").format(error_msg), error=True)
 
     def gotId(self, data, file_):
         """Called when a progress id has been received
@@ -343,14 +497,17 @@
         @param file_(str): file path
         """
         try:
-            self.progress_id = data['progress']
+            self.progress_id = data["progress"]
         except KeyError:
             # TODO: if 'xmlui' key is present, manage xmlui message display
             self.disp(_(u"Can't upload file"), error=True)
             self.host.quit(2)
 
     def error(self, failure):
-        self.disp(_("Error while trying to upload a file: {reason}").format(reason=failure), error=True)
+        self.disp(
+            _("Error while trying to upload a file: {reason}").format(reason=failure),
+            error=True,
+        )
         self.host.quit(1)
 
     def start(self):
@@ -362,31 +519,58 @@
             self.disp(_(u"[{}] is a dir! Can't upload a dir").format(file_))
             self.host.quit(1)
 
-        self.full_dest_jid = self.host.get_full_jid(self.args.jid) if self.args.jid is not None else ''
+        self.full_dest_jid = (
+            self.host.get_full_jid(self.args.jid) if self.args.jid is not None else ""
+        )
         options = {}
         if self.args.ignore_tls_errors:
-            options['ignore_tls_errors'] = C.BOOL_TRUE
+            options["ignore_tls_errors"] = C.BOOL_TRUE
 
         path = os.path.abspath(file_)
-        self.host.bridge.fileUpload(path, '', self.full_dest_jid, options, self.profile, callback=lambda pid, file_=file_: self.gotId(pid, file_), errback=self.error)
+        self.host.bridge.fileUpload(
+            path,
+            "",
+            self.full_dest_jid,
+            options,
+            self.profile,
+            callback=lambda pid, file_=file_: self.gotId(pid, file_),
+            errback=self.error,
+        )
 
 
 class ShareList(base.CommandBase):
-
     def __init__(self, host):
-        extra_outputs = {'default': self.default_output}
-        super(ShareList, self).__init__(host, 'list', use_output=C.OUTPUT_LIST_DICT, extra_outputs=extra_outputs, help=_(u'retrieve files shared by an entity'), use_verbose=True)
-        self.need_loop=True
+        extra_outputs = {"default": self.default_output}
+        super(ShareList, self).__init__(
+            host,
+            "list",
+            use_output=C.OUTPUT_LIST_DICT,
+            extra_outputs=extra_outputs,
+            help=_(u"retrieve files shared by an entity"),
+            use_verbose=True,
+        )
+        self.need_loop = True
 
     def add_parser_options(self):
-        self.parser.add_argument("-d", "--path", default=u'', help=_(u"path to the directory containing the files"))
-        self.parser.add_argument("jid", type=base.unicode_decoder, nargs='?', default='', help=_("jid of sharing entity (nothing to check our own jid)"))
+        self.parser.add_argument(
+            "-d",
+            "--path",
+            default=u"",
+            help=_(u"path to the directory containing the files"),
+        )
+        self.parser.add_argument(
+            "jid",
+            type=base.unicode_decoder,
+            nargs="?",
+            default="",
+            help=_("jid of sharing entity (nothing to check our own jid)"),
+        )
 
     def file_gen(self, files_data):
         for file_data in files_data:
-            yield file_data[u'name']
-            yield file_data.get(u'size', '')
-            yield file_data.get(u'hash','')
+            yield file_data[u"name"]
+            yield file_data.get(u"size", "")
+            yield file_data.get(u"hash", "")
 
     def _name_filter(self, name, row):
         if row.type == C.FILE_TYPE_DIRECTORY:
@@ -394,42 +578,41 @@
         elif row.type == C.FILE_TYPE_FILE:
             return A.color(C.A_FILE, name)
         else:
-            self.disp(_(u'unknown file type: {type}').format(type=row.type), error=True)
+            self.disp(_(u"unknown file type: {type}").format(type=row.type), error=True)
             return name
 
     def _size_filter(self, size, row):
         if not size:
-            return u''
+            return u""
         size = int(size)
-        # cf. https://stackoverflow.com/a/1094933 (thanks)
-        suffix = u'o'
-        for unit in [u'', u'Ki', u'Mi', u'Gi', u'Ti', u'Pi', u'Ei', u'Zi']:
+        #  cf. https://stackoverflow.com/a/1094933 (thanks)
+        suffix = u"o"
+        for unit in [u"", u"Ki", u"Mi", u"Gi", u"Ti", u"Pi", u"Ei", u"Zi"]:
             if abs(size) < 1024.0:
                 return A.color(A.BOLD, u"{:.2f}".format(size), unit, suffix)
             size /= 1024.0
 
-        return A.color(A.BOLD, u"{:.2f}".format(size), u'Yi', suffix)
+        return A.color(A.BOLD, u"{:.2f}".format(size), u"Yi", suffix)
 
     def default_output(self, files_data):
         """display files a way similar to ls"""
-        files_data.sort(key=lambda d: d['name'].lower())
+        files_data.sort(key=lambda d: d["name"].lower())
         show_header = False
         if self.verbosity == 0:
-            headers = (u'name', u'type')
+            headers = (u"name", u"type")
         elif self.verbosity == 1:
-            headers = (u'name', u'type', u'size')
+            headers = (u"name", u"type", u"size")
         elif self.verbosity > 1:
             show_header = True
-            headers = (u'name', u'type', u'size', u'hash')
-        table = common.Table.fromDict(self.host,
-                                      files_data,
-                                      headers,
-                                      filters={u'name': self._name_filter,
-                                               u'size': self._size_filter},
-                                      defaults={u'size': u'',
-                                                u'hash': u''},
-                                      )
-        table.display_blank(show_header=show_header, hide_cols=['type'])
+            headers = (u"name", u"type", u"size", u"hash")
+        table = common.Table.fromDict(
+            self.host,
+            files_data,
+            headers,
+            filters={u"name": self._name_filter, u"size": self._size_filter},
+            defaults={u"size": u"", u"hash": u""},
+        )
+        table.display_blank(show_header=show_header, hide_cols=["type"])
 
     def _FISListCb(self, files_data):
         self.output(files_data)
@@ -442,40 +625,66 @@
             {},
             self.profile,
             callback=self._FISListCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't retrieve shared files: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
+            errback=partial(
+                self.errback,
+                msg=_(u"can't retrieve shared files: {}"),
+                exit_code=C.EXIT_BRIDGE_ERRBACK,
+            ),
+        )
 
 
 class SharePath(base.CommandBase):
-
     def __init__(self, host):
-        super(SharePath, self).__init__(host, 'path', help=_(u'share a file or directory'), use_verbose=True)
-        self.need_loop=True
+        super(SharePath, self).__init__(
+            host, "path", help=_(u"share a file or directory"), use_verbose=True
+        )
+        self.need_loop = True
 
     def add_parser_options(self):
-        self.parser.add_argument("-n", "--name", type=base.unicode_decoder, default=u'', help=_(u"virtual name to use (default: use directory/file name)"))
+        self.parser.add_argument(
+            "-n",
+            "--name",
+            type=base.unicode_decoder,
+            default=u"",
+            help=_(u"virtual name to use (default: use directory/file name)"),
+        )
         perm_group = self.parser.add_mutually_exclusive_group()
-        perm_group.add_argument("-j", "--jid", type=base.unicode_decoder, action='append', dest="jids", default=[], help=_(u"jid of contacts allowed to retrieve the files"))
-        perm_group.add_argument("--public", action='store_true', help=_(u"share publicly the file(s) (/!\\ *everybody* will be able to access them)"))
-        self.parser.add_argument("path", type=base.unicode_decoder, help=_(u"path to a file or directory to share"))
-
+        perm_group.add_argument(
+            "-j",
+            "--jid",
+            type=base.unicode_decoder,
+            action="append",
+            dest="jids",
+            default=[],
+            help=_(u"jid of contacts allowed to retrieve the files"),
+        )
+        perm_group.add_argument(
+            "--public",
+            action="store_true",
+            help=_(
+                u"share publicly the file(s) (/!\\ *everybody* will be able to access them)"
+            ),
+        )
+        self.parser.add_argument(
+            "path",
+            type=base.unicode_decoder,
+            help=_(u"path to a file or directory to share"),
+        )
 
     def _FISSharePathCb(self, name):
-        self.disp(_(u'{path} shared under the name "{name}"').format(
-            path = self.path,
-            name = name))
+        self.disp(
+            _(u'{path} shared under the name "{name}"').format(path=self.path, name=name)
+        )
         self.host.quit()
 
     def start(self):
         self.path = os.path.abspath(self.args.path)
         if self.args.public:
-            access = {u'read': {u'type': u'public'}}
+            access = {u"read": {u"type": u"public"}}
         else:
             jids = self.args.jids
             if jids:
-                access = {u'read': {u'type': 'whitelist',
-                                         u'jids': jids}}
+                access = {u"read": {u"type": "whitelist", u"jids": jids}}
             else:
                 access = {}
         self.host.bridge.FISSharePath(
@@ -484,20 +693,27 @@
             json.dumps(access, ensure_ascii=False),
             self.profile,
             callback=self._FISSharePathCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't share path: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
+            errback=partial(
+                self.errback,
+                msg=_(u"can't share path: {}"),
+                exit_code=C.EXIT_BRIDGE_ERRBACK,
+            ),
+        )
 
 
 class Share(base.CommandBase):
     subcommands = (ShareList, SharePath)
 
     def __init__(self, host):
-        super(Share, self).__init__(host, 'share', use_profile=False, help=_(u'files sharing management'))
+        super(Share, self).__init__(
+            host, "share", use_profile=False, help=_(u"files sharing management")
+        )
 
 
 class File(base.CommandBase):
     subcommands = (Send, Request, Receive, Upload, Share)
 
     def __init__(self, host):
-        super(File, self).__init__(host, 'file', use_profile=False, help=_(u'files sending/receiving/management'))
+        super(File, self).__init__(
+            host, "file", use_profile=False, help=_(u"files sending/receiving/management")
+        )
--- a/sat_frontends/jp/cmd_forums.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/jp/cmd_forums.py	Wed Jun 27 20:14:46 2018 +0200
@@ -33,23 +33,36 @@
 
 
 class Edit(base.CommandBase, common.BaseEdit):
-    use_items=False
+    use_items = False
 
     def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'edit', use_pubsub=True, use_draft=True, use_verbose=True, help=_(u'edit forums'))
+        base.CommandBase.__init__(
+            self,
+            host,
+            "edit",
+            use_pubsub=True,
+            use_draft=True,
+            use_verbose=True,
+            help=_(u"edit forums"),
+        )
         common.BaseEdit.__init__(self, self.host, FORUMS_TMP_DIR)
-        self.need_loop=True
+        self.need_loop = True
 
     def add_parser_options(self):
-        self.parser.add_argument("-k", "--key", type=base.unicode_decoder, default=u'',
-                                 help=_(u"forum key (DEFAULT: default forums)"))
+        self.parser.add_argument(
+            "-k",
+            "--key",
+            type=base.unicode_decoder,
+            default=u"",
+            help=_(u"forum key (DEFAULT: default forums)"),
+        )
 
     def getTmpSuff(self):
         """return suffix used for content file"""
-        return u'json'
+        return u"json"
 
     def forumsSetCb(self):
-        self.disp(_(u'forums have been edited'), 1)
+        self.disp(_(u"forums have been edited"), 1)
         self.host.quit()
 
     def publish(self, forums_raw):
@@ -60,9 +73,12 @@
             self.args.key,
             self.profile,
             callback=self.forumsSetCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't set forums: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
+            errback=partial(
+                self.errback,
+                msg=_(u"can't set forums: {}"),
+                exit_code=C.EXIT_BRIDGE_ERRBACK,
+            ),
+        )
 
     def forumsGetCb(self, forums_json):
         content_file_obj, content_file_path = self.getTmpFile()
@@ -71,19 +87,21 @@
             # we loads and dumps to have pretty printed json
             forums = json.loads(forums_json)
             # cf. https://stackoverflow.com/a/18337754
-            f = codecs.getwriter('utf-8')(content_file_obj)
+            f = codecs.getwriter("utf-8")(content_file_obj)
             json.dump(forums, f, ensure_ascii=False, indent=4)
             content_file_obj.seek(0)
         self.runEditor("forums_editor_args", content_file_path, content_file_obj)
 
     def forumsGetEb(self, failure_):
         # FIXME: error handling with bridge is broken, need to be properly fixed
-        if failure_.condition == u'item-not-found':
-            self.forumsGetCb(u'')
+        if failure_.condition == u"item-not-found":
+            self.forumsGetCb(u"")
         else:
-            self.errback(failure_,
-                         msg=_(u"can't get forums structure: {}"),
-                         exit_code=C.EXIT_BRIDGE_ERRBACK)
+            self.errback(
+                failure_,
+                msg=_(u"can't get forums structure: {}"),
+                exit_code=C.EXIT_BRIDGE_ERRBACK,
+            )
 
     def start(self):
         self.host.bridge.forumsGet(
@@ -92,55 +110,66 @@
             self.args.key,
             self.profile,
             callback=self.forumsGetCb,
-            errback=self.forumsGetEb)
+            errback=self.forumsGetEb,
+        )
 
 
 class Get(base.CommandBase):
-
     def __init__(self, host):
-        extra_outputs = {'default': self.default_output}
-        base.CommandBase.__init__(self, host, 'get', use_output=C.OUTPUT_COMPLEX, extra_outputs=extra_outputs, use_pubsub=True, use_verbose=True, help=_(u'get forums structure'))
-        self.need_loop=True
+        extra_outputs = {"default": self.default_output}
+        base.CommandBase.__init__(
+            self,
+            host,
+            "get",
+            use_output=C.OUTPUT_COMPLEX,
+            extra_outputs=extra_outputs,
+            use_pubsub=True,
+            use_verbose=True,
+            help=_(u"get forums structure"),
+        )
+        self.need_loop = True
 
     def add_parser_options(self):
-        self.parser.add_argument("-k", "--key", type=base.unicode_decoder, default=u'',
-                                 help=_(u"forum key (DEFAULT: default forums)"))
+        self.parser.add_argument(
+            "-k",
+            "--key",
+            type=base.unicode_decoder,
+            default=u"",
+            help=_(u"forum key (DEFAULT: default forums)"),
+        )
 
     def default_output(self, forums, level=0):
         for forum in forums:
             keys = list(forum.keys())
             keys.sort()
             try:
-                keys.remove(u'title')
+                keys.remove(u"title")
             except ValueError:
                 pass
             else:
-                keys.insert(0, u'title')
+                keys.insert(0, u"title")
             try:
-                keys.remove(u'sub-forums')
+                keys.remove(u"sub-forums")
             except ValueError:
                 pass
             else:
-                keys.append(u'sub-forums')
+                keys.append(u"sub-forums")
 
             for key in keys:
                 value = forum[key]
-                if key == 'sub-forums':
-                    self.default_output(value, level+1)
+                if key == "sub-forums":
+                    self.default_output(value, level + 1)
                 else:
-                    if self.host.verbosity < 1 and key != u'title':
+                    if self.host.verbosity < 1 and key != u"title":
                         continue
                     head_color = C.A_LEVEL_COLORS[level % len(C.A_LEVEL_COLORS)]
-                    self.disp(A.color(level * 4 * u' ',
-                                      head_color,
-                                      key,
-                                      A.RESET,
-                                      u': ',
-                                      value))
+                    self.disp(
+                        A.color(level * 4 * u" ", head_color, key, A.RESET, u": ", value)
+                    )
 
     def forumsGetCb(self, forums_raw):
         if not forums_raw:
-            self.disp(_(u'no schema found'), 1)
+            self.disp(_(u"no schema found"), 1)
             self.host.quit(1)
         forums = json.loads(forums_raw)
         self.output(forums)
@@ -153,14 +182,18 @@
             self.args.key,
             self.profile,
             callback=self.forumsGetCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't get forums: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
-
+            errback=partial(
+                self.errback,
+                msg=_(u"can't get forums: {}"),
+                exit_code=C.EXIT_BRIDGE_ERRBACK,
+            ),
+        )
 
 
 class Forums(base.CommandBase):
     subcommands = (Get, Edit)
 
     def __init__(self, host):
-        super(Forums, self).__init__(host, 'forums', use_profile=False, help=_(u'Forums structure edition'))
+        super(Forums, self).__init__(
+            host, "forums", use_profile=False, help=_(u"Forums structure edition")
+        )
--- a/sat_frontends/jp/cmd_identity.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/jp/cmd_identity.py	Wed Jun 27 20:14:46 2018 +0200
@@ -29,18 +29,21 @@
 
 
 class Get(base.CommandBase):
-
     def __init__(self, host):
-        base.CommandBase.__init__(self,
-                                  host,
-                                  'get',
-                                  use_output=C.OUTPUT_DICT,
-                                  use_verbose=True,
-                                  help=_(u'get identity data'))
-        self.need_loop=True
+        base.CommandBase.__init__(
+            self,
+            host,
+            "get",
+            use_output=C.OUTPUT_DICT,
+            use_verbose=True,
+            help=_(u"get identity data"),
+        )
+        self.need_loop = True
 
     def add_parser_options(self):
-        self.parser.add_argument("jid", type=base.unicode_decoder, help=_(u"entity to check"))
+        self.parser.add_argument(
+            "jid", type=base.unicode_decoder, help=_(u"entity to check")
+        )
 
     def identityGetCb(self, data):
         self.output(data)
@@ -52,19 +55,31 @@
             jid_,
             self.profile,
             callback=self.identityGetCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't get identity data: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
+            errback=partial(
+                self.errback,
+                msg=_(u"can't get identity data: {}"),
+                exit_code=C.EXIT_BRIDGE_ERRBACK,
+            ),
+        )
 
 
 class Set(base.CommandBase):
     def __init__(self, host):
-        super(Set, self).__init__(host, 'set', help=_('modify an existing event'))
+        super(Set, self).__init__(host, "set", help=_("modify an existing event"))
 
     def add_parser_options(self):
-        self.parser.add_argument("-f", "--field", type=base.unicode_decoder, action='append', nargs=2, dest='fields',
-                                 metavar=(u"KEY", u"VALUE"), required=True, help=_(u"identity field(s) to set"))
-        self.need_loop=True
+        self.parser.add_argument(
+            "-f",
+            "--field",
+            type=base.unicode_decoder,
+            action="append",
+            nargs=2,
+            dest="fields",
+            metavar=(u"KEY", u"VALUE"),
+            required=True,
+            help=_(u"identity field(s) to set"),
+        )
+        self.need_loop = True
 
     def start(self):
         fields = dict(self.args.fields)
@@ -72,13 +87,18 @@
             fields,
             self.profile,
             callback=self.host.quit,
-            errback=partial(self.errback,
-                            msg=_(u"can't set identity data data: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
+            errback=partial(
+                self.errback,
+                msg=_(u"can't set identity data data: {}"),
+                exit_code=C.EXIT_BRIDGE_ERRBACK,
+            ),
+        )
 
 
 class Identity(base.CommandBase):
     subcommands = (Get, Set)
 
     def __init__(self, host):
-        super(Identity, self).__init__(host, 'identity', use_profile=False, help=_('identity management'))
+        super(Identity, self).__init__(
+            host, "identity", use_profile=False, help=_("identity management")
+        )
--- a/sat_frontends/jp/cmd_input.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/jp/cmd_input.py	Wed Jun 27 20:14:46 2018 +0200
@@ -28,21 +28,22 @@
 import sys
 
 __commands__ = ["Input"]
-OPT_STDIN = 'stdin'
-OPT_SHORT = 'short'
-OPT_LONG = 'long'
-OPT_POS = 'positional'
-OPT_IGNORE = 'ignore'
+OPT_STDIN = "stdin"
+OPT_SHORT = "short"
+OPT_LONG = "long"
+OPT_POS = "positional"
+OPT_IGNORE = "ignore"
 OPT_TYPES = (OPT_STDIN, OPT_SHORT, OPT_LONG, OPT_POS, OPT_IGNORE)
-OPT_EMPTY_SKIP = 'skip'
-OPT_EMPTY_IGNORE = 'ignore'
+OPT_EMPTY_SKIP = "skip"
+OPT_EMPTY_IGNORE = "ignore"
 OPT_EMPTY_CHOICES = (OPT_EMPTY_SKIP, OPT_EMPTY_IGNORE)
 
 
 class InputCommon(base.CommandBase):
-
     def __init__(self, host, name, help):
-        base.CommandBase.__init__(self, host, name, use_verbose=True, use_profile=False, help=help)
+        base.CommandBase.__init__(
+            self, host, name, use_verbose=True, use_profile=False, help=help
+        )
         self.idx = 0
         self.reset()
 
@@ -54,15 +55,61 @@
         self._values_ori = []
 
     def add_parser_options(self):
-        self.parser.add_argument("--encoding", default='utf-8', help=_(u"encoding of the input data"))
-        self.parser.add_argument("-i", "--stdin", action='append_const', const=(OPT_STDIN, None), dest='arguments', help=_(u"standard input"))
-        self.parser.add_argument("-s", "--short", type=self.opt(OPT_SHORT), action='append', dest='arguments', help=_(u"short option"))
-        self.parser.add_argument("-l", "--long", type=self.opt(OPT_LONG), action='append', dest='arguments', help=_(u"long option"))
-        self.parser.add_argument("-p", "--positional", type=self.opt(OPT_POS), action='append', dest='arguments', help=_(u"positional argument"))
-        self.parser.add_argument("-x", "--ignore", action='append_const', const=(OPT_IGNORE, None), dest='arguments', help=_(u"ignore value"))
-        self.parser.add_argument("-D", "--debug", action='store_true', help=_(u"don't actually run commands but echo what would be launched"))
-        self.parser.add_argument("--log", type=argparse.FileType('wb'), help=_(u"log stdout to FILE"))
-        self.parser.add_argument("--log-err", type=argparse.FileType('wb'), help=_(u"log stderr to FILE"))
+        self.parser.add_argument(
+            "--encoding", default="utf-8", help=_(u"encoding of the input data")
+        )
+        self.parser.add_argument(
+            "-i",
+            "--stdin",
+            action="append_const",
+            const=(OPT_STDIN, None),
+            dest="arguments",
+            help=_(u"standard input"),
+        )
+        self.parser.add_argument(
+            "-s",
+            "--short",
+            type=self.opt(OPT_SHORT),
+            action="append",
+            dest="arguments",
+            help=_(u"short option"),
+        )
+        self.parser.add_argument(
+            "-l",
+            "--long",
+            type=self.opt(OPT_LONG),
+            action="append",
+            dest="arguments",
+            help=_(u"long option"),
+        )
+        self.parser.add_argument(
+            "-p",
+            "--positional",
+            type=self.opt(OPT_POS),
+            action="append",
+            dest="arguments",
+            help=_(u"positional argument"),
+        )
+        self.parser.add_argument(
+            "-x",
+            "--ignore",
+            action="append_const",
+            const=(OPT_IGNORE, None),
+            dest="arguments",
+            help=_(u"ignore value"),
+        )
+        self.parser.add_argument(
+            "-D",
+            "--debug",
+            action="store_true",
+            help=_(u"don't actually run commands but echo what would be launched"),
+        )
+        self.parser.add_argument(
+            "--log", type=argparse.FileType("wb"), help=_(u"log stdout to FILE")
+        )
+        self.parser.add_argument(
+            "--log-err", type=argparse.FileType("wb"), help=_(u"log stderr to FILE")
+        )
         self.parser.add_argument("command", nargs=argparse.REMAINDER)
 
     def opt(self, type_):
@@ -75,7 +122,10 @@
         try:
             arg_type, arg_name = arguments[self.args_idx]
         except IndexError:
-            self.disp(_(u"arguments in input data and in arguments sequence don't match"), error=True)
+            self.disp(
+                _(u"arguments in input data and in arguments sequence don't match"),
+                error=True,
+            )
             self.host.quit(C.EXIT_DATA_ERROR)
         self.args_idx += 1
         while self.args_idx < len(arguments):
@@ -97,8 +147,16 @@
         if value is False:
             # we skip the whole row
             if self.args.debug:
-                self.disp(A.color(C.A_SUBHEADER, _(u'values: '), A.RESET, u', '.join(self._values_ori)), 2)
-                self.disp(A.color(A.BOLD, _(u'**SKIPPING**\n')))
+                self.disp(
+                    A.color(
+                        C.A_SUBHEADER,
+                        _(u"values: "),
+                        A.RESET,
+                        u", ".join(self._values_ori),
+                    ),
+                    2,
+                )
+                self.disp(A.color(A.BOLD, _(u"**SKIPPING**\n")))
             self.reset()
             self.idx += 1
             raise exceptions.CancelError
@@ -108,61 +166,81 @@
 
         for v in value:
             if arg_type == OPT_STDIN:
-                self._stdin.append(v.encode('utf-8'))
+                self._stdin.append(v.encode("utf-8"))
             elif arg_type == OPT_SHORT:
-                self._opts.append('-{}'.format(arg_name))
-                self._opts.append(v.encode('utf-8'))
+                self._opts.append("-{}".format(arg_name))
+                self._opts.append(v.encode("utf-8"))
             elif arg_type == OPT_LONG:
-                self._opts.append('--{}'.format(arg_name))
-                self._opts.append(v.encode('utf-8'))
+                self._opts.append("--{}".format(arg_name))
+                self._opts.append(v.encode("utf-8"))
             elif arg_type == OPT_POS:
-                self._pos.append(v.encode('utf-8'))
+                self._pos.append(v.encode("utf-8"))
             elif arg_type == OPT_IGNORE:
                 pass
             else:
-                self.parser.error(_(u"Invalid argument, an option type is expected, got {type_}:{name}").format(
-                                  type_=arg_type, name=arg_name))
+                self.parser.error(
+                    _(
+                        u"Invalid argument, an option type is expected, got {type_}:{name}"
+                    ).format(type_=arg_type, name=arg_name)
+                )
 
     def runCommand(self):
         """run requested command with parsed arguments"""
         if self.args_idx != len(self.args.arguments):
-            self.disp(_(u"arguments in input data and in arguments sequence don't match"), error=True)
+            self.disp(
+                _(u"arguments in input data and in arguments sequence don't match"),
+                error=True,
+            )
             self.host.quit(C.EXIT_DATA_ERROR)
-        self.disp(A.color(C.A_HEADER, _(u'command {idx}').format(idx=self.idx)), no_lf=not self.args.debug)
-        stdin = ''.join(self._stdin)
+        self.disp(
+            A.color(C.A_HEADER, _(u"command {idx}").format(idx=self.idx)),
+            no_lf=not self.args.debug,
+        )
+        stdin = "".join(self._stdin)
         if self.args.debug:
-            self.disp(A.color(C.A_SUBHEADER, _(u'values: '), A.RESET, u', '.join(self._values_ori)), 2)
+            self.disp(
+                A.color(
+                    C.A_SUBHEADER, _(u"values: "), A.RESET, u", ".join(self._values_ori)
+                ),
+                2,
+            )
 
             if stdin:
-                self.disp(A.color(C.A_SUBHEADER, u'--- STDIN ---'))
-                self.disp(stdin.decode('utf-8'))
-                self.disp(A.color(C.A_SUBHEADER, u'-------------'))
-            self.disp(u'{indent}{prog} {static} {options} {positionals}'.format(
-                indent = 4*u' ',
-                prog=sys.argv[0],
-                static = ' '.join(self.args.command).decode('utf-8'),
-                options = u' '.join([o.decode('utf-8') for o in self._opts]),
-                positionals = u' '.join([p.decode('utf-8') for p in self._pos])
-                ))
-            self.disp(u'\n')
+                self.disp(A.color(C.A_SUBHEADER, u"--- STDIN ---"))
+                self.disp(stdin.decode("utf-8"))
+                self.disp(A.color(C.A_SUBHEADER, u"-------------"))
+            self.disp(
+                u"{indent}{prog} {static} {options} {positionals}".format(
+                    indent=4 * u" ",
+                    prog=sys.argv[0],
+                    static=" ".join(self.args.command).decode("utf-8"),
+                    options=u" ".join([o.decode("utf-8") for o in self._opts]),
+                    positionals=u" ".join([p.decode("utf-8") for p in self._pos]),
+                )
+            )
+            self.disp(u"\n")
         else:
-            self.disp(u' (' + u', '.join(self._values_ori) + u')', 2, no_lf=True)
+            self.disp(u" (" + u", ".join(self._values_ori) + u")", 2, no_lf=True)
             args = [sys.argv[0]] + self.args.command + self._opts + self._pos
-            p = subprocess.Popen(args,
-                                  stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+            p = subprocess.Popen(
+                args,
+                stdin=subprocess.PIPE,
+                stdout=subprocess.PIPE,
+                stderr=subprocess.PIPE,
+            )
             (stdout, stderr) = p.communicate(stdin)
             log = self.args.log
             log_err = self.args.log_err
-            log_tpl = '{command}\n{buff}\n\n'
+            log_tpl = "{command}\n{buff}\n\n"
             if log:
-                log.write(log_tpl.format(command=' '.join(args), buff=stdout))
+                log.write(log_tpl.format(command=" ".join(args), buff=stdout))
             if log_err:
-                log_err.write(log_tpl.format(command=' '.join(args), buff=stderr))
+                log_err.write(log_tpl.format(command=" ".join(args), buff=stderr))
             ret = p.wait()
             if ret == 0:
-                self.disp(A.color(C.A_SUCCESS, _(u'OK')))
+                self.disp(A.color(C.A_SUCCESS, _(u"OK")))
             else:
-                self.disp(A.color(C.A_FAILURE, _(u'FAILED')))
+                self.disp(A.color(C.A_FAILURE, _(u"FAILED")))
 
         self.reset()
         self.idx += 1
@@ -181,32 +259,57 @@
 
 
 class Csv(InputCommon):
-
     def __init__(self, host):
-        super(Csv, self).__init__(host, 'csv', _(u'comma-separated values'))
+        super(Csv, self).__init__(host, "csv", _(u"comma-separated values"))
 
     def add_parser_options(self):
         InputCommon.add_parser_options(self)
-        self.parser.add_argument("-r", "--row", type=int, default=0, help=_(u"starting row (previous ones will be ignored)"))
-        self.parser.add_argument("-S", "--split", action='append_const', const=('split', None), dest='arguments', help=_(u"split value in several options"))
-        self.parser.add_argument("-E", "--empty", action='append', type=self.opt('empty'), dest='arguments',
-                                 help=_(u"action to do on empty value ({choices})").format(choices=u', '.join(OPT_EMPTY_CHOICES)))
+        self.parser.add_argument(
+            "-r",
+            "--row",
+            type=int,
+            default=0,
+            help=_(u"starting row (previous ones will be ignored)"),
+        )
+        self.parser.add_argument(
+            "-S",
+            "--split",
+            action="append_const",
+            const=("split", None),
+            dest="arguments",
+            help=_(u"split value in several options"),
+        )
+        self.parser.add_argument(
+            "-E",
+            "--empty",
+            action="append",
+            type=self.opt("empty"),
+            dest="arguments",
+            help=_(u"action to do on empty value ({choices})").format(
+                choices=u", ".join(OPT_EMPTY_CHOICES)
+            ),
+        )
 
     def filter(self, filter_type, filter_arg, value):
-        if filter_type == 'split':
+        if filter_type == "split":
             return value.split()
-        elif filter_type == 'empty':
+        elif filter_type == "empty":
             if filter_arg == OPT_EMPTY_IGNORE:
                 return value if value else None
             elif filter_arg == OPT_EMPTY_SKIP:
                 return value if value else False
             else:
-                self.parser.error(_(u"--empty value must be one of {choices}").format(choices=u', '.join(OPT_EMPTY_CHOICES)))
+                self.parser.error(
+                    _(u"--empty value must be one of {choices}").format(
+                        choices=u", ".join(OPT_EMPTY_CHOICES)
+                    )
+                )
 
         super(Csv, self).filter(filter_type, filter_arg, value)
 
     def start(self):
         import csv
+
         reader = csv.reader(sys.stdin)
         for idx, row in enumerate(reader):
             try:
@@ -216,7 +319,7 @@
                     self.addValue(value.decode(self.args.encoding))
                 self.runCommand()
             except exceptions.CancelError:
-                # this row has been cancelled, we skip it
+                #  this row has been cancelled, we skip it
                 continue
 
 
@@ -224,4 +327,9 @@
     subcommands = (Csv,)
 
     def __init__(self, host):
-        super(Input, self).__init__(host, 'input', use_profile=False, help=_(u'launch command with external input'))
+        super(Input, self).__init__(
+            host,
+            "input",
+            use_profile=False,
+            help=_(u"launch command with external input"),
+        )
--- a/sat_frontends/jp/cmd_invitation.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/jp/cmd_invitation.py	Wed Jun 27 20:14:46 2018 +0200
@@ -29,32 +29,111 @@
 
 
 class Create(base.CommandBase):
-
     def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'create', use_profile=False, use_output=C.OUTPUT_DICT, help=_(u'create and send an invitation'))
-        self.need_loop=True
+        base.CommandBase.__init__(
+            self,
+            host,
+            "create",
+            use_profile=False,
+            use_output=C.OUTPUT_DICT,
+            help=_(u"create and send an invitation"),
+        )
+        self.need_loop = True
 
     def add_parser_options(self):
-        self.parser.add_argument("-j", "--jid", type=base.unicode_decoder, default='', help='jid of the invitee (default: generate one)')
-        self.parser.add_argument("-P", "--password", type=base.unicode_decoder, default='', help='password of the invitee profile/XMPP account (default: generate one)')
-        self.parser.add_argument("-n", "--name", type=base.unicode_decoder, default='', help='name of the invitee')
-        self.parser.add_argument("-N", "--host-name", type=base.unicode_decoder, default='', help='name of the host')
-        self.parser.add_argument("-e", "--email", action="append", type=base.unicode_decoder, default=[], help='email(s) to send the invitation to (if --no-email is set, email will just be saved)')
-        self.parser.add_argument("--no-email", action="store_true", help='do NOT send invitation email')
-        self.parser.add_argument("-l", "--lang", type=base.unicode_decoder, default='', help='main language spoken by the invitee')
-        self.parser.add_argument("-u", "--url", type=base.unicode_decoder, default='', help='template to construct the URL')
-        self.parser.add_argument("-s", "--subject", type=base.unicode_decoder, default='', help='subject of the invitation email (default: generic subject)')
-        self.parser.add_argument("-b", "--body", type=base.unicode_decoder, default='', help='body of the invitation email (default: generic body)')
-        self.parser.add_argument("-x", "--extra", metavar=('KEY', 'VALUE'), type=base.unicode_decoder, action='append', nargs=2, default=[], help='extra data to associate with invitation/invitee')
-        self.parser.add_argument("-p", "--profile", type=base.unicode_decoder, default='', help="profile doing the invitation (default: don't associate profile)")
+        self.parser.add_argument(
+            "-j",
+            "--jid",
+            type=base.unicode_decoder,
+            default="",
+            help="jid of the invitee (default: generate one)",
+        )
+        self.parser.add_argument(
+            "-P",
+            "--password",
+            type=base.unicode_decoder,
+            default="",
+            help="password of the invitee profile/XMPP account (default: generate one)",
+        )
+        self.parser.add_argument(
+            "-n",
+            "--name",
+            type=base.unicode_decoder,
+            default="",
+            help="name of the invitee",
+        )
+        self.parser.add_argument(
+            "-N",
+            "--host-name",
+            type=base.unicode_decoder,
+            default="",
+            help="name of the host",
+        )
+        self.parser.add_argument(
+            "-e",
+            "--email",
+            action="append",
+            type=base.unicode_decoder,
+            default=[],
+            help="email(s) to send the invitation to (if --no-email is set, email will just be saved)",
+        )
+        self.parser.add_argument(
+            "--no-email", action="store_true", help="do NOT send invitation email"
+        )
+        self.parser.add_argument(
+            "-l",
+            "--lang",
+            type=base.unicode_decoder,
+            default="",
+            help="main language spoken by the invitee",
+        )
+        self.parser.add_argument(
+            "-u",
+            "--url",
+            type=base.unicode_decoder,
+            default="",
+            help="template to construct the URL",
+        )
+        self.parser.add_argument(
+            "-s",
+            "--subject",
+            type=base.unicode_decoder,
+            default="",
+            help="subject of the invitation email (default: generic subject)",
+        )
+        self.parser.add_argument(
+            "-b",
+            "--body",
+            type=base.unicode_decoder,
+            default="",
+            help="body of the invitation email (default: generic body)",
+        )
+        self.parser.add_argument(
+            "-x",
+            "--extra",
+            metavar=("KEY", "VALUE"),
+            type=base.unicode_decoder,
+            action="append",
+            nargs=2,
+            default=[],
+            help="extra data to associate with invitation/invitee",
+        )
+        self.parser.add_argument(
+            "-p",
+            "--profile",
+            type=base.unicode_decoder,
+            default="",
+            help="profile doing the invitation (default: don't associate profile)",
+        )
 
     def invitationCreateCb(self, invitation_data):
         self.output(invitation_data)
         self.host.quit(C.EXIT_OK)
 
     def invitationCreateEb(self, failure_):
-        self.disp(u"can't create invitation: {reason}".format(
-            reason=failure_), error=True)
+        self.disp(
+            u"can't create invitation: {reason}".format(reason=failure_), error=True
+        )
         self.host.quit(C.EXIT_BRIDGE_ERRBACK)
 
     def start(self):
@@ -63,11 +142,13 @@
         emails_extra = self.args.email[1:]
         if self.args.no_email:
             if email:
-                extra['email'] = email
-                data_format.iter2dict(u'emails_extra', emails_extra)
+                extra["email"] = email
+                data_format.iter2dict(u"emails_extra", emails_extra)
         else:
             if not email:
-                self.parser.error(_(u'you need to specify an email address to send email invitation'))
+                self.parser.error(
+                    _(u"you need to specify an email address to send email invitation")
+                )
 
         self.host.bridge.invitationCreate(
             email,
@@ -83,46 +164,66 @@
             extra,
             self.args.profile,
             callback=self.invitationCreateCb,
-            errback=self.invitationCreateEb)
+            errback=self.invitationCreateEb,
+        )
 
 
 class Get(base.CommandBase):
-
     def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'get', use_profile=False, use_output=C.OUTPUT_DICT, help=_(u'get invitation data'))
-        self.need_loop=True
+        base.CommandBase.__init__(
+            self,
+            host,
+            "get",
+            use_profile=False,
+            use_output=C.OUTPUT_DICT,
+            help=_(u"get invitation data"),
+        )
+        self.need_loop = True
 
     def add_parser_options(self):
-        self.parser.add_argument("id", type=base.unicode_decoder,
-                                 help=_(u"invitation UUID"))
-        self.parser.add_argument("-j", "--with-jid", action="store_true", help=_(u"start profile session and retrieve jid"))
+        self.parser.add_argument(
+            "id", type=base.unicode_decoder, help=_(u"invitation UUID")
+        )
+        self.parser.add_argument(
+            "-j",
+            "--with-jid",
+            action="store_true",
+            help=_(u"start profile session and retrieve jid"),
+        )
 
     def output_data(self, data, jid_=None):
         if jid_ is not None:
-            data['jid'] = jid_
+            data["jid"] = jid_
         self.output(data)
         self.host.quit()
 
     def invitationGetCb(self, invitation_data):
         if self.args.with_jid:
-            profile = invitation_data[u'guest_profile']
+            profile = invitation_data[u"guest_profile"]
+
             def session_started(dummy):
                 self.host.bridge.asyncGetParamA(
-                    u'JabberID',
-                    u'Connection',
+                    u"JabberID",
+                    u"Connection",
                     profile_key=profile,
                     callback=lambda jid_: self.output_data(invitation_data, jid_),
-                    errback=partial(self.errback,
-                                    msg=_(u"can't retrieve jid: {}"),
-                                    exit_code=C.EXIT_BRIDGE_ERRBACK))
+                    errback=partial(
+                        self.errback,
+                        msg=_(u"can't retrieve jid: {}"),
+                        exit_code=C.EXIT_BRIDGE_ERRBACK,
+                    ),
+                )
 
             self.host.bridge.profileStartSession(
-                invitation_data[u'password'],
+                invitation_data[u"password"],
                 profile,
                 callback=session_started,
-                errback=partial(self.errback,
-                                msg=_(u"can't start session: {}"),
-                                exit_code=C.EXIT_BRIDGE_ERRBACK))
+                errback=partial(
+                    self.errback,
+                    msg=_(u"can't start session: {}"),
+                    exit_code=C.EXIT_BRIDGE_ERRBACK,
+                ),
+            )
         else:
             self.output_data(invitation_data)
 
@@ -130,61 +231,120 @@
         self.host.bridge.invitationGet(
             self.args.id,
             callback=self.invitationGetCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't get invitation data: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
+            errback=partial(
+                self.errback,
+                msg=_(u"can't get invitation data: {}"),
+                exit_code=C.EXIT_BRIDGE_ERRBACK,
+            ),
+        )
 
 
 class Modify(base.CommandBase):
-
     def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'modify', use_profile=False, help=_(u'modify existing invitation'))
-        self.need_loop=True
+        base.CommandBase.__init__(
+            self, host, "modify", use_profile=False, help=_(u"modify existing invitation")
+        )
+        self.need_loop = True
 
     def add_parser_options(self):
-        self.parser.add_argument("--replace", action='store_true', help='replace the whole data')
-        self.parser.add_argument("-n", "--name", type=base.unicode_decoder, default='', help='name of the invitee')
-        self.parser.add_argument("-N", "--host-name", type=base.unicode_decoder, default='', help='name of the host')
-        self.parser.add_argument("-e", "--email", type=base.unicode_decoder, default='', help='email to send the invitation to (if --no-email is set, email will just be saved)')
-        self.parser.add_argument("-l", "--lang", dest="language", type=base.unicode_decoder, default='',
-            help='main language spoken by the invitee')
-        self.parser.add_argument("-x", "--extra", metavar=('KEY', 'VALUE'), type=base.unicode_decoder, action='append', nargs=2, default=[], help='extra data to associate with invitation/invitee')
-        self.parser.add_argument("-p", "--profile", type=base.unicode_decoder, default='', help="profile doing the invitation (default: don't associate profile")
-        self.parser.add_argument("id", type=base.unicode_decoder,
-                                 help=_(u"invitation UUID"))
+        self.parser.add_argument(
+            "--replace", action="store_true", help="replace the whole data"
+        )
+        self.parser.add_argument(
+            "-n",
+            "--name",
+            type=base.unicode_decoder,
+            default="",
+            help="name of the invitee",
+        )
+        self.parser.add_argument(
+            "-N",
+            "--host-name",
+            type=base.unicode_decoder,
+            default="",
+            help="name of the host",
+        )
+        self.parser.add_argument(
+            "-e",
+            "--email",
+            type=base.unicode_decoder,
+            default="",
+            help="email to send the invitation to (if --no-email is set, email will just be saved)",
+        )
+        self.parser.add_argument(
+            "-l",
+            "--lang",
+            dest="language",
+            type=base.unicode_decoder,
+            default="",
+            help="main language spoken by the invitee",
+        )
+        self.parser.add_argument(
+            "-x",
+            "--extra",
+            metavar=("KEY", "VALUE"),
+            type=base.unicode_decoder,
+            action="append",
+            nargs=2,
+            default=[],
+            help="extra data to associate with invitation/invitee",
+        )
+        self.parser.add_argument(
+            "-p",
+            "--profile",
+            type=base.unicode_decoder,
+            default="",
+            help="profile doing the invitation (default: don't associate profile",
+        )
+        self.parser.add_argument(
+            "id", type=base.unicode_decoder, help=_(u"invitation UUID")
+        )
 
     def invitationModifyCb(self):
-        self.disp(_(u'invitations have been modified correctly'))
+        self.disp(_(u"invitations have been modified correctly"))
         self.host.quit(C.EXIT_OK)
 
     def invitationModifyEb(self, failure_):
-        self.disp(u"can't create invitation: {reason}".format(
-            reason=failure_), error=True)
+        self.disp(
+            u"can't create invitation: {reason}".format(reason=failure_), error=True
+        )
         self.host.quit(C.EXIT_BRIDGE_ERRBACK)
 
     def start(self):
         extra = dict(self.args.extra)
-        for arg_name in ('name', 'host_name', 'email', 'language', 'profile'):
+        for arg_name in ("name", "host_name", "email", "language", "profile"):
             value = getattr(self.args, arg_name)
             if not value:
                 continue
             if arg_name in extra:
-                self.parser.error(_(u"you can't set {arg_name} in both optional argument and extra").format(arg_name=arg_name))
+                self.parser.error(
+                    _(
+                        u"you can't set {arg_name} in both optional argument and extra"
+                    ).format(arg_name=arg_name)
+                )
             extra[arg_name] = value
         self.host.bridge.invitationModify(
             self.args.id,
             extra,
             self.args.replace,
             callback=self.invitationModifyCb,
-            errback=self.invitationModifyEb)
+            errback=self.invitationModifyEb,
+        )
 
 
 class List(base.CommandBase):
-
     def __init__(self, host):
-        extra_outputs = {'default': self.default_output}
-        base.CommandBase.__init__(self, host, 'list', use_profile=False, use_output=C.OUTPUT_COMPLEX, extra_outputs=extra_outputs, help=_(u'list invitations data'))
-        self.need_loop=True
+        extra_outputs = {"default": self.default_output}
+        base.CommandBase.__init__(
+            self,
+            host,
+            "list",
+            use_profile=False,
+            use_output=C.OUTPUT_COMPLEX,
+            extra_outputs=extra_outputs,
+            help=_(u"list invitations data"),
+        )
+        self.need_loop = True
 
     def default_output(self, data):
         for idx, datum in enumerate(data.iteritems()):
@@ -192,12 +352,17 @@
                 self.disp(u"\n")
             key, invitation_data = datum
             self.disp(A.color(C.A_HEADER, key))
-            indent = u'  '
+            indent = u"  "
             for k, v in invitation_data.iteritems():
-                self.disp(indent + A.color(C.A_SUBHEADER, k + u':') + u' ' + unicode(v))
+                self.disp(indent + A.color(C.A_SUBHEADER, k + u":") + u" " + unicode(v))
 
     def add_parser_options(self):
-        self.parser.add_argument("-p", "--profile", default=C.PROF_KEY_NONE, help=_(u"return only invitations linked to this profile"))
+        self.parser.add_argument(
+            "-p",
+            "--profile",
+            default=C.PROF_KEY_NONE,
+            help=_(u"return only invitations linked to this profile"),
+        )
 
     def invitationListCb(self, data):
         self.output(data)
@@ -207,13 +372,21 @@
         self.host.bridge.invitationList(
             self.args.profile,
             callback=self.invitationListCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't list invitations: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
+            errback=partial(
+                self.errback,
+                msg=_(u"can't list invitations: {}"),
+                exit_code=C.EXIT_BRIDGE_ERRBACK,
+            ),
+        )
 
 
 class Invitation(base.CommandBase):
     subcommands = (Create, Get, Modify, List)
 
     def __init__(self, host):
-        super(Invitation, self).__init__(host, 'invitation', use_profile=False, help=_(u'invitation of user(s) without XMPP account'))
+        super(Invitation, self).__init__(
+            host,
+            "invitation",
+            use_profile=False,
+            help=_(u"invitation of user(s) without XMPP account"),
+        )
--- a/sat_frontends/jp/cmd_merge_request.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/jp/cmd_merge_request.py	Wed Jun 27 20:14:46 2018 +0200
@@ -30,18 +30,47 @@
 
 
 class Set(base.CommandBase):
-
     def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'set', use_pubsub=True,
-                                  pubsub_defaults = {u'service': _(u'auto'), u'node': _(u'auto')},
-                                  help=_(u'publish or update a merge request'))
-        self.need_loop=True
+        base.CommandBase.__init__(
+            self,
+            host,
+            "set",
+            use_pubsub=True,
+            pubsub_defaults={u"service": _(u"auto"), u"node": _(u"auto")},
+            help=_(u"publish or update a merge request"),
+        )
+        self.need_loop = True
 
     def add_parser_options(self):
-        self.parser.add_argument("-i", "--item", type=base.unicode_decoder, default=u'', help=_(u"id or URL of the request to update, or nothing for a new one"))
-        self.parser.add_argument("-r", "--repository", metavar="PATH", type=base.unicode_decoder, default=u'.', help=_(u"path of the repository (DEFAULT: current directory)"))
-        self.parser.add_argument("-f", "--force", action="store_true", help=_(u"publish merge request without confirmation"))
-        self.parser.add_argument("-l", "--label", dest="labels", type=base.unicode_decoder, action='append', help=_(u"labels to categorize your request"))
+        self.parser.add_argument(
+            "-i",
+            "--item",
+            type=base.unicode_decoder,
+            default=u"",
+            help=_(u"id or URL of the request to update, or nothing for a new one"),
+        )
+        self.parser.add_argument(
+            "-r",
+            "--repository",
+            metavar="PATH",
+            type=base.unicode_decoder,
+            default=u".",
+            help=_(u"path of the repository (DEFAULT: current directory)"),
+        )
+        self.parser.add_argument(
+            "-f",
+            "--force",
+            action="store_true",
+            help=_(u"publish merge request without confirmation"),
+        )
+        self.parser.add_argument(
+            "-l",
+            "--label",
+            dest="labels",
+            type=base.unicode_decoder,
+            action="append",
+            help=_(u"labels to categorize your request"),
+        )
 
     def mergeRequestSetCb(self, published_id):
         if published_id:
@@ -51,45 +80,54 @@
         self.host.quit(C.EXIT_OK)
 
     def sendRequest(self):
-        extra = {'update': 'true'} if self.args.item else {}
+        extra = {"update": "true"} if self.args.item else {}
         values = {}
         if self.args.labels is not None:
-            values[u'labels'] = self.args.labels
+            values[u"labels"] = self.args.labels
         self.host.bridge.mergeRequestSet(
             self.args.service,
             self.args.node,
             self.repository,
-            u'auto',
+            u"auto",
             values,
-            u'',
+            u"",
             self.args.item,
             extra,
             self.profile,
             callback=self.mergeRequestSetCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't create merge request: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
+            errback=partial(
+                self.errback,
+                msg=_(u"can't create merge request: {}"),
+                exit_code=C.EXIT_BRIDGE_ERRBACK,
+            ),
+        )
 
     def askConfirmation(self):
         if not self.args.force:
-            message = _(u"You are going to publish your changes to service [{service}], are you sure ?").format(
-                service=self.args.service)
+            message = _(
+                u"You are going to publish your changes to service [{service}], are you sure ?"
+            ).format(service=self.args.service)
             self.host.confirmOrQuit(message, _(u"merge request publication cancelled"))
         self.sendRequest()
 
     def start(self):
         self.repository = os.path.expanduser(os.path.abspath(self.args.repository))
-        common.URIFinder(self, self.repository, 'merge requests', self.askConfirmation)
+        common.URIFinder(self, self.repository, "merge requests", self.askConfirmation)
 
 
 class Get(base.CommandBase):
-
     def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'get', use_verbose=True,
-                                  use_pubsub=True, pubsub_flags={C.MULTI_ITEMS},
-                                  pubsub_defaults = {u'service': _(u'auto'), u'node': _(u'auto')},
-                                  help=_(u'get a merge request'))
-        self.need_loop=True
+        base.CommandBase.__init__(
+            self,
+            host,
+            "get",
+            use_verbose=True,
+            use_pubsub=True,
+            pubsub_flags={C.MULTI_ITEMS},
+            pubsub_defaults={u"service": _(u"auto"), u"node": _(u"auto")},
+            help=_(u"get a merge request"),
+        )
+        self.need_loop = True
 
     def add_parser_options(self):
         pass
@@ -98,11 +136,11 @@
         if self.verbosity >= 1:
             whitelist = None
         else:
-            whitelist = {'id', 'title', 'body'}
+            whitelist = {"id", "title", "body"}
         for request_xmlui in requests_data[0]:
             xmlui = xmlui_manager.create(self.host, request_xmlui, whitelist=whitelist)
             xmlui.show(values_only=True)
-            self.disp(u'')
+            self.disp(u"")
         self.host.quit(C.EXIT_OK)
 
     def getRequests(self):
@@ -112,29 +150,45 @@
             self.args.node,
             self.args.max,
             self.args.items,
-            u'',
+            u"",
             extra,
             self.profile,
             callback=self.mergeRequestGetCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't get merge request: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
+            errback=partial(
+                self.errback,
+                msg=_(u"can't get merge request: {}"),
+                exit_code=C.EXIT_BRIDGE_ERRBACK,
+            ),
+        )
 
     def start(self):
-        common.URIFinder(self, os.getcwd(), 'merge requests', self.getRequests, meta_map={})
+        common.URIFinder(
+            self, os.getcwd(), "merge requests", self.getRequests, meta_map={}
+        )
 
 
 class Import(base.CommandBase):
-
     def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'import',
-                                  use_pubsub=True, pubsub_flags={C.SINGLE_ITEM, C.ITEM},
-                                  pubsub_defaults = {u'service': _(u'auto'), u'node': _(u'auto')},
-                                  help=_(u'import a merge request'))
-        self.need_loop=True
+        base.CommandBase.__init__(
+            self,
+            host,
+            "import",
+            use_pubsub=True,
+            pubsub_flags={C.SINGLE_ITEM, C.ITEM},
+            pubsub_defaults={u"service": _(u"auto"), u"node": _(u"auto")},
+            help=_(u"import a merge request"),
+        )
+        self.need_loop = True
 
     def add_parser_options(self):
-        self.parser.add_argument("-r", "--repository", metavar="PATH", type=base.unicode_decoder, default=u'.', help=_(u"path of the repository (DEFAULT: current directory)"))
+        self.parser.add_argument(
+            "-r",
+            "--repository",
+            metavar="PATH",
+            type=base.unicode_decoder,
+            default=u".",
+            help=_(u"path of the repository (DEFAULT: current directory)"),
+        )
 
     def mergeRequestImportCb(self):
         self.host.quit(C.EXIT_OK)
@@ -149,17 +203,24 @@
             extra,
             self.profile,
             callback=self.mergeRequestImportCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't import merge request: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
+            errback=partial(
+                self.errback,
+                msg=_(u"can't import merge request: {}"),
+                exit_code=C.EXIT_BRIDGE_ERRBACK,
+            ),
+        )
 
     def start(self):
         self.repository = os.path.expanduser(os.path.abspath(self.args.repository))
-        common.URIFinder(self, self.repository, 'merge requests', self.importRequest, meta_map={})
+        common.URIFinder(
+            self, self.repository, "merge requests", self.importRequest, meta_map={}
+        )
 
 
 class MergeRequest(base.CommandBase):
     subcommands = (Set, Get, Import)
 
     def __init__(self, host):
-        super(MergeRequest, self).__init__(host, 'merge-request', use_profile=False, help=_('merge-request management'))
+        super(MergeRequest, self).__init__(
+            host, "merge-request", use_profile=False, help=_("merge-request management")
+        )
--- a/sat_frontends/jp/cmd_message.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/jp/cmd_message.py	Wed Jun 27 20:14:46 2018 +0200
@@ -27,25 +27,58 @@
 
 
 class Send(base.CommandBase):
-
     def __init__(self, host):
-        super(Send, self).__init__(host, 'send', help=_('send a message to a contact'))
+        super(Send, self).__init__(host, "send", help=_("send a message to a contact"))
 
     def add_parser_options(self):
-        self.parser.add_argument("-l", "--lang", type=str, default='', help=_(u"language of the message"))
-        self.parser.add_argument("-s", "--separate", action="store_true", help=_(u"separate xmpp messages: send one message per line instead of one message alone."))
-        self.parser.add_argument("-n", "--new-line", action="store_true", help=_(u"add a new line at the beginning of the input (usefull for ascii art ;))"))
-        self.parser.add_argument("-S", "--subject", type=base.unicode_decoder, help=_(u"subject of the message"))
-        self.parser.add_argument("-L", "--subject_lang", type=str, default='', help=_(u"language of subject"))
-        self.parser.add_argument("-t", "--type", choices=C.MESS_TYPE_STANDARD + (C.MESS_TYPE_AUTO,), default=C.MESS_TYPE_AUTO, help=_("type of the message"))
+        self.parser.add_argument(
+            "-l", "--lang", type=str, default="", help=_(u"language of the message")
+        )
+        self.parser.add_argument(
+            "-s",
+            "--separate",
+            action="store_true",
+            help=_(
+                u"separate xmpp messages: send one message per line instead of one message alone."
+            ),
+        )
+        self.parser.add_argument(
+            "-n",
+            "--new-line",
+            action="store_true",
+            help=_(
+                u"add a new line at the beginning of the input (usefull for ascii art ;))"
+            ),
+        )
+        self.parser.add_argument(
+            "-S",
+            "--subject",
+            type=base.unicode_decoder,
+            help=_(u"subject of the message"),
+        )
+        self.parser.add_argument(
+            "-L", "--subject_lang", type=str, default="", help=_(u"language of subject")
+        )
+        self.parser.add_argument(
+            "-t",
+            "--type",
+            choices=C.MESS_TYPE_STANDARD + (C.MESS_TYPE_AUTO,),
+            default=C.MESS_TYPE_AUTO,
+            help=_("type of the message"),
+        )
         syntax = self.parser.add_mutually_exclusive_group()
         syntax.add_argument("-x", "--xhtml", action="store_true", help=_(u"XHTML body"))
         syntax.add_argument("-r", "--rich", action="store_true", help=_(u"rich body"))
-        self.parser.add_argument("jid", type=base.unicode_decoder, help=_(u"the destination jid"))
+        self.parser.add_argument(
+            "jid", type=base.unicode_decoder, help=_(u"the destination jid")
+        )
 
     def start(self):
         if self.args.xhtml and self.args.separate:
-            self.disp(u"argument -s/--separate is not compatible yet with argument -x/--xhtml", error=True)
+            self.disp(
+                u"argument -s/--separate is not compatible yet with argument -x/--xhtml",
+                error=True,
+            )
             self.host.quit(2)
 
         jids = self.host.check_jids([self.args.jid])
@@ -58,7 +91,9 @@
         @param dest_jid: destination jid
         """
         header = "\n" if self.args.new_line else ""
-        stdin_lines = [stream.decode('utf-8','ignore') for stream in sys.stdin.readlines()]
+        stdin_lines = [
+            stream.decode("utf-8", "ignore") for stream in sys.stdin.readlines()
+        ]
         extra = {}
         if self.args.subject is None:
             subject = {}
@@ -72,20 +107,52 @@
             extra[key] = clean_ustr(u"".join(stdin_lines))
             stdin_lines = []
 
-        if self.args.separate:  #we send stdin in several messages
+        if self.args.separate:  # we send stdin in several messages
             if header:
-                self.host.bridge.messageSend(dest_jid, {self.args.lang: header}, subject, self.args.type, profile_key=self.profile, callback=lambda: None, errback=lambda ignore: ignore)
+                self.host.bridge.messageSend(
+                    dest_jid,
+                    {self.args.lang: header},
+                    subject,
+                    self.args.type,
+                    profile_key=self.profile,
+                    callback=lambda: None,
+                    errback=lambda ignore: ignore,
+                )
 
             for line in stdin_lines:
-                self.host.bridge.messageSend(dest_jid, {self.args.lang: line.replace("\n","")}, subject, self.args.type, extra, profile_key=self.host.profile, callback=lambda: None, errback=lambda ignore: ignore)
+                self.host.bridge.messageSend(
+                    dest_jid,
+                    {self.args.lang: line.replace("\n", "")},
+                    subject,
+                    self.args.type,
+                    extra,
+                    profile_key=self.host.profile,
+                    callback=lambda: None,
+                    errback=lambda ignore: ignore,
+                )
 
         else:
-            msg = {self.args.lang: header + clean_ustr(u"".join(stdin_lines))} if not (self.args.xhtml or self.args.rich) else {}
-            self.host.bridge.messageSend(dest_jid, msg, subject, self.args.type, extra, profile_key=self.host.profile, callback=lambda: None, errback=lambda ignore: ignore)
+            msg = (
+                {self.args.lang: header + clean_ustr(u"".join(stdin_lines))}
+                if not (self.args.xhtml or self.args.rich)
+                else {}
+            )
+            self.host.bridge.messageSend(
+                dest_jid,
+                msg,
+                subject,
+                self.args.type,
+                extra,
+                profile_key=self.host.profile,
+                callback=lambda: None,
+                errback=lambda ignore: ignore,
+            )
 
 
 class Message(base.CommandBase):
-    subcommands = (Send, )
+    subcommands = (Send,)
 
     def __init__(self, host):
-        super(Message, self).__init__(host, 'message', use_profile=False, help=_('messages handling'))
+        super(Message, self).__init__(
+            host, "message", use_profile=False, help=_("messages handling")
+        )
--- a/sat_frontends/jp/cmd_pipe.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/jp/cmd_pipe.py	Wed Jun 27 20:14:46 2018 +0200
@@ -23,7 +23,7 @@
 import sys
 from sat.core.i18n import _
 from sat_frontends.tools import jid
-import xml.etree.ElementTree as ET # FIXME: used temporarily to manage XMLUI
+import xml.etree.ElementTree as ET  # FIXME: used temporarily to manage XMLUI
 from functools import partial
 import socket
 import SocketServer
@@ -33,18 +33,20 @@
 
 START_PORT = 9999
 
+
 class PipeOut(base.CommandBase):
-
     def __init__(self, host):
-        super(PipeOut, self).__init__(host, 'out', help=_('send a pipe a stream'))
+        super(PipeOut, self).__init__(host, "out", help=_("send a pipe a stream"))
         self.need_loop = True
 
     def add_parser_options(self):
-        self.parser.add_argument("jid", type=base.unicode_decoder, help=_("the destination jid"))
+        self.parser.add_argument(
+            "jid", type=base.unicode_decoder, help=_("the destination jid")
+        )
 
     def streamOutCb(self, port):
         s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-        s.connect(('127.0.0.1', int(port)))
+        s.connect(("127.0.0.1", int(port)))
         while True:
             buf = sys.stdin.read(4096)
             if not buf:
@@ -53,7 +55,7 @@
                 s.sendall(buf)
             except socket.error as e:
                 if e.errno == errno.EPIPE:
-                    sys.stderr.write(str(e) + '\n')
+                    sys.stderr.write(str(e) + "\n")
                     self.host.quit(1)
                 else:
                     raise e
@@ -65,13 +67,15 @@
             self.host.get_full_jid(self.args.jid),
             self.profile,
             callback=self.streamOutCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't start stream: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
+            errback=partial(
+                self.errback,
+                msg=_(u"can't start stream: {}"),
+                exit_code=C.EXIT_BRIDGE_ERRBACK,
+            ),
+        )
 
 
 class StreamServer(SocketServer.BaseRequestHandler):
-
     def handle(self):
         while True:
             data = self.request.recv(4096)
@@ -81,33 +85,37 @@
             try:
                 sys.stdout.flush()
             except IOError as e:
-                sys.stderr.write(str(e) + '\n')
+                sys.stderr.write(str(e) + "\n")
                 break
-        # calling shutdown will do a deadlock as we don't use separate thread
+        #  calling shutdown will do a deadlock as we don't use separate thread
         # this is a workaround (cf. https://stackoverflow.com/a/36017741)
         self.server._BaseServer__shutdown_request = True
 
 
 class PipeIn(base.CommandAnswering):
-
     def __init__(self, host):
-        super(PipeIn, self).__init__(host, 'in', help=_('receive a pipe stream'))
+        super(PipeIn, self).__init__(host, "in", help=_("receive a pipe stream"))
         self.action_callbacks = {"STREAM": self.onStreamAction}
 
     def add_parser_options(self):
-        self.parser.add_argument("jids", type=base.unicode_decoder, nargs="*", help=_('Jids accepted (none means "accept everything")'))
+        self.parser.add_argument(
+            "jids",
+            type=base.unicode_decoder,
+            nargs="*",
+            help=_('Jids accepted (none means "accept everything")'),
+        )
 
     def getXmluiId(self, action_data):
         # FIXME: we temporarily use ElementTree, but a real XMLUI managing module
         #        should be available in the future
         # TODO: XMLUI module
         try:
-            xml_ui = action_data['xmlui']
+            xml_ui = action_data["xmlui"]
         except KeyError:
             self.disp(_(u"Action has no XMLUI"), 1)
         else:
-            ui = ET.fromstring(xml_ui.encode('utf-8'))
-            xmlui_id = ui.get('submit')
+            ui = ET.fromstring(xml_ui.encode("utf-8"))
+            xmlui_id = ui.get("submit")
             if not xmlui_id:
                 self.disp(_(u"Invalid XMLUI received"), error=True)
             return xmlui_id
@@ -117,7 +125,7 @@
         if xmlui_id is None:
             return self.host.quitFromSignal(1)
         try:
-            from_jid = jid.JID(action_data['meta_from_jid'])
+            from_jid = jid.JID(action_data["meta_from_jid"])
         except KeyError:
             self.disp(_(u"Ignoring action without from_jid data"), 1)
             return
@@ -134,8 +142,7 @@
                         raise e
                 else:
                     break
-            xmlui_data = {'answer': C.BOOL_TRUE,
-                          'port': unicode(port)}
+            xmlui_data = {"answer": C.BOOL_TRUE, "port": unicode(port)}
             self.host.bridge.launchAction(xmlui_id, xmlui_data, profile_key=profile)
             server.serve_forever()
             self.host.quitFromSignal()
@@ -148,4 +155,6 @@
     subcommands = (PipeOut, PipeIn)
 
     def __init__(self, host):
-        super(Pipe, self).__init__(host, 'pipe', use_profile=False, help=_('stream piping through XMPP'))
+        super(Pipe, self).__init__(
+            host, "pipe", use_profile=False, help=_("stream piping through XMPP")
+        )
--- a/sat_frontends/jp/cmd_pubsub.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/jp/cmd_pubsub.py	Wed Jun 27 20:14:46 2018 +0200
@@ -38,36 +38,52 @@
 
 PUBSUB_TMP_DIR = u"pubsub"
 PUBSUB_SCHEMA_TMP_DIR = PUBSUB_TMP_DIR + "_schema"
-ALLOWED_SUBSCRIPTIONS_OWNER = ('subscribed', 'pending', 'none')
+ALLOWED_SUBSCRIPTIONS_OWNER = ("subscribed", "pending", "none")
 
 # TODO: need to split this class in several modules, plugin should handle subcommands
 
 
 class NodeInfo(base.CommandBase):
-
     def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'info', use_output=C.OUTPUT_DICT, use_pubsub=True, pubsub_flags={C.NODE}, help=_(u'retrieve node configuration'))
-        self.need_loop=True
+        base.CommandBase.__init__(
+            self,
+            host,
+            "info",
+            use_output=C.OUTPUT_DICT,
+            use_pubsub=True,
+            pubsub_flags={C.NODE},
+            help=_(u"retrieve node configuration"),
+        )
+        self.need_loop = True
 
     def add_parser_options(self):
-        self.parser.add_argument("-k", "--key", type=base.unicode_decoder, action='append', dest='keys',
-                                 help=_(u"data key to filter"))
+        self.parser.add_argument(
+            "-k",
+            "--key",
+            type=base.unicode_decoder,
+            action="append",
+            dest="keys",
+            help=_(u"data key to filter"),
+        )
 
     def removePrefix(self, key):
         return key[7:] if key.startswith(u"pubsub#") else key
 
     def filterKey(self, key):
-        return any((key == k or key == u'pubsub#' + k) for k in self.args.keys)
+        return any((key == k or key == u"pubsub#" + k) for k in self.args.keys)
 
     def psNodeConfigurationGetCb(self, config_dict):
         key_filter = (lambda k: True) if not self.args.keys else self.filterKey
-        config_dict = {self.removePrefix(k):v for k,v in config_dict.iteritems() if key_filter(k)}
+        config_dict = {
+            self.removePrefix(k): v for k, v in config_dict.iteritems() if key_filter(k)
+        }
         self.output(config_dict)
         self.host.quit()
 
     def psNodeConfigurationGetEb(self, failure_):
-        self.disp(u"can't get node configuration: {reason}".format(
-            reason=failure_), error=True)
+        self.disp(
+            u"can't get node configuration: {reason}".format(reason=failure_), error=True
+        )
         self.host.quit(C.EXIT_BRIDGE_ERRBACK)
 
     def start(self):
@@ -76,36 +92,58 @@
             self.args.node,
             self.profile,
             callback=self.psNodeConfigurationGetCb,
-            errback=self.psNodeConfigurationGetEb)
+            errback=self.psNodeConfigurationGetEb,
+        )
 
 
 class NodeCreate(base.CommandBase):
-
     def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'create', use_output=C.OUTPUT_DICT, use_pubsub=True, pubsub_flags={C.NODE}, use_verbose=True, help=_(u'create a node'))
-        self.need_loop=True
+        base.CommandBase.__init__(
+            self,
+            host,
+            "create",
+            use_output=C.OUTPUT_DICT,
+            use_pubsub=True,
+            pubsub_flags={C.NODE},
+            use_verbose=True,
+            help=_(u"create a node"),
+        )
+        self.need_loop = True
 
     def add_parser_options(self):
-        self.parser.add_argument("-f", "--field", type=base.unicode_decoder, action='append', nargs=2, dest='fields',
-                                 default=[], metavar=(u"KEY", u"VALUE"), help=_(u"configuration field to set"))
-        self.parser.add_argument("-F", "--full-prefix", action="store_true", help=_(u"don't prepend \"pubsub#\" prefix to field names"))
+        self.parser.add_argument(
+            "-f",
+            "--field",
+            type=base.unicode_decoder,
+            action="append",
+            nargs=2,
+            dest="fields",
+            default=[],
+            metavar=(u"KEY", u"VALUE"),
+            help=_(u"configuration field to set"),
+        )
+        self.parser.add_argument(
+            "-F",
+            "--full-prefix",
+            action="store_true",
+            help=_(u'don\'t prepend "pubsub#" prefix to field names'),
+        )
 
     def psNodeCreateCb(self, node_id):
         if self.host.verbosity:
-            announce = _(u'node created successfully: ')
+            announce = _(u"node created successfully: ")
         else:
-            announce = u''
+            announce = u""
         self.disp(announce + node_id)
         self.host.quit()
 
     def psNodeCreateEb(self, failure_):
-        self.disp(u"can't create: {reason}".format(
-            reason=failure_), error=True)
+        self.disp(u"can't create: {reason}".format(reason=failure_), error=True)
         self.host.quit(C.EXIT_BRIDGE_ERRBACK)
 
     def start(self):
         if not self.args.full_prefix:
-            options = {u'pubsub#' + k: v for k,v in self.args.fields}
+            options = {u"pubsub#" + k: v for k, v in self.args.fields}
         else:
             options = dict(self.args.fields)
         self.host.bridge.psNodeCreate(
@@ -114,32 +152,48 @@
             options,
             self.profile,
             callback=self.psNodeCreateCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't create node: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
+            errback=partial(
+                self.errback,
+                msg=_(u"can't create node: {}"),
+                exit_code=C.EXIT_BRIDGE_ERRBACK,
+            ),
+        )
 
 
 class NodeDelete(base.CommandBase):
-
     def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'delete', use_pubsub=True, pubsub_flags={C.NODE}, help=_(u'delete a node'))
-        self.need_loop=True
+        base.CommandBase.__init__(
+            self,
+            host,
+            "delete",
+            use_pubsub=True,
+            pubsub_flags={C.NODE},
+            help=_(u"delete a node"),
+        )
+        self.need_loop = True
 
     def add_parser_options(self):
-        self.parser.add_argument('-f', '--force', action='store_true', help=_(u'delete node without confirmation'))
+        self.parser.add_argument(
+            "-f",
+            "--force",
+            action="store_true",
+            help=_(u"delete node without confirmation"),
+        )
 
     def psNodeDeleteCb(self):
-        self.disp(_(u'node [{node}] deleted successfully').format(node=self.args.node))
+        self.disp(_(u"node [{node}] deleted successfully").format(node=self.args.node))
         self.host.quit()
 
     def start(self):
         if not self.args.force:
             if not self.args.service:
                 message = _(u"Are you sure to delete pep node [{node_id}] ?").format(
-                    node_id=self.args.node)
+                    node_id=self.args.node
+                )
             else:
-                message = _(u"Are you sure to delete node [{node_id}] on service [{service}] ?").format(
-                    node_id=self.args.node, service=self.args.service)
+                message = _(
+                    u"Are you sure to delete node [{node_id}] on service [{service}] ?"
+                ).format(node_id=self.args.node, service=self.args.service)
             self.host.confirmOrQuit(message, _(u"node deletion cancelled"))
 
         self.host.bridge.psNodeDelete(
@@ -147,33 +201,54 @@
             self.args.node,
             self.profile,
             callback=self.psNodeDeleteCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't delete node: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
+            errback=partial(
+                self.errback,
+                msg=_(u"can't delete node: {}"),
+                exit_code=C.EXIT_BRIDGE_ERRBACK,
+            ),
+        )
 
 
 class NodeSet(base.CommandBase):
-
     def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'set', use_output=C.OUTPUT_DICT, use_pubsub=True, pubsub_flags={C.NODE}, use_verbose=True, help=_(u'set node configuration'))
-        self.need_loop=True
+        base.CommandBase.__init__(
+            self,
+            host,
+            "set",
+            use_output=C.OUTPUT_DICT,
+            use_pubsub=True,
+            pubsub_flags={C.NODE},
+            use_verbose=True,
+            help=_(u"set node configuration"),
+        )
+        self.need_loop = True
 
     def add_parser_options(self):
-        self.parser.add_argument("-f", "--field", type=base.unicode_decoder, action='append', nargs=2, dest='fields',
-                                 required=True, metavar=(u"KEY", u"VALUE"), help=_(u"configuration field to set (required)"))
+        self.parser.add_argument(
+            "-f",
+            "--field",
+            type=base.unicode_decoder,
+            action="append",
+            nargs=2,
+            dest="fields",
+            required=True,
+            metavar=(u"KEY", u"VALUE"),
+            help=_(u"configuration field to set (required)"),
+        )
 
     def psNodeConfigurationSetCb(self):
-        self.disp(_(u'node configuration successful'), 1)
+        self.disp(_(u"node configuration successful"), 1)
         self.host.quit()
 
     def psNodeConfigurationSetEb(self, failure_):
-        self.disp(u"can't set node configuration: {reason}".format(
-            reason=failure_), error=True)
+        self.disp(
+            u"can't set node configuration: {reason}".format(reason=failure_), error=True
+        )
         self.host.quit(C.EXIT_BRIDGE_ERRBACK)
 
     def getKeyName(self, k):
-        if not k.startswith(u'pubsub#'):
-            return u'pubsub#' + k
+        if not k.startswith(u"pubsub#"):
+            return u"pubsub#" + k
         else:
             return k
 
@@ -181,17 +256,25 @@
         self.host.bridge.psNodeConfigurationSet(
             self.args.service,
             self.args.node,
-            {self.getKeyName(k): v for k,v in self.args.fields},
+            {self.getKeyName(k): v for k, v in self.args.fields},
             self.profile,
             callback=self.psNodeConfigurationSetCb,
-            errback=self.psNodeConfigurationSetEb)
+            errback=self.psNodeConfigurationSetEb,
+        )
 
 
 class NodeAffiliationsGet(base.CommandBase):
-
     def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'get', use_output=C.OUTPUT_DICT, use_pubsub=True, pubsub_flags={C.NODE}, help=_(u'retrieve node affiliations (for node owner)'))
-        self.need_loop=True
+        base.CommandBase.__init__(
+            self,
+            host,
+            "get",
+            use_output=C.OUTPUT_DICT,
+            use_pubsub=True,
+            pubsub_flags={C.NODE},
+            help=_(u"retrieve node affiliations (for node owner)"),
+        )
+        self.need_loop = True
 
     def add_parser_options(self):
         pass
@@ -201,8 +284,9 @@
         self.host.quit()
 
     def psNodeAffiliationsGetEb(self, failure_):
-        self.disp(u"can't get node affiliations: {reason}".format(
-            reason=failure_), error=True)
+        self.disp(
+            u"can't get node affiliations: {reason}".format(reason=failure_), error=True
+        )
         self.host.quit(C.EXIT_BRIDGE_ERRBACK)
 
     def start(self):
@@ -211,35 +295,46 @@
             self.args.node,
             self.profile,
             callback=self.psNodeAffiliationsGetCb,
-            errback=self.psNodeAffiliationsGetEb)
+            errback=self.psNodeAffiliationsGetEb,
+        )
 
 
 class NodeAffiliationsSet(base.CommandBase):
-
     def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'set', use_pubsub=True, pubsub_flags={C.NODE}, use_verbose=True, help=_(u'set affiliations (for node owner)'))
-        self.need_loop=True
+        base.CommandBase.__init__(
+            self,
+            host,
+            "set",
+            use_pubsub=True,
+            pubsub_flags={C.NODE},
+            use_verbose=True,
+            help=_(u"set affiliations (for node owner)"),
+        )
+        self.need_loop = True
 
     def add_parser_options(self):
         # XXX: we use optional argument syntax for a required one because list of list of 2 elements
         #      (uses to construct dicts) don't work with positional arguments
-        self.parser.add_argument("-a",
-                                 "--affiliation",
-                                 dest="affiliations",
-                                 metavar=('JID', 'AFFILIATION'),
-                                 required=True,
-                                 type=base.unicode_decoder,
-                                 action="append",
-                                 nargs=2,
-                                 help=_(u"entity/affiliation couple(s)"))
+        self.parser.add_argument(
+            "-a",
+            "--affiliation",
+            dest="affiliations",
+            metavar=("JID", "AFFILIATION"),
+            required=True,
+            type=base.unicode_decoder,
+            action="append",
+            nargs=2,
+            help=_(u"entity/affiliation couple(s)"),
+        )
 
     def psNodeAffiliationsSetCb(self):
         self.disp(_(u"affiliations have been set"), 1)
         self.host.quit()
 
     def psNodeAffiliationsSetEb(self, failure_):
-        self.disp(u"can't set node affiliations: {reason}".format(
-            reason=failure_), error=True)
+        self.disp(
+            u"can't set node affiliations: {reason}".format(reason=failure_), error=True
+        )
         self.host.quit(C.EXIT_BRIDGE_ERRBACK)
 
     def start(self):
@@ -250,21 +345,34 @@
             affiliations,
             self.profile,
             callback=self.psNodeAffiliationsSetCb,
-            errback=self.psNodeAffiliationsSetEb)
+            errback=self.psNodeAffiliationsSetEb,
+        )
 
 
 class NodeAffiliations(base.CommandBase):
     subcommands = (NodeAffiliationsGet, NodeAffiliationsSet)
 
     def __init__(self, host):
-        super(NodeAffiliations, self).__init__(host, 'affiliations', use_profile=False, help=_(u'set or retrieve node affiliations'))
+        super(NodeAffiliations, self).__init__(
+            host,
+            "affiliations",
+            use_profile=False,
+            help=_(u"set or retrieve node affiliations"),
+        )
 
 
 class NodeSubscriptionsGet(base.CommandBase):
-
     def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'get', use_output=C.OUTPUT_DICT, use_pubsub=True, pubsub_flags={C.NODE}, help=_(u'retrieve node subscriptions (for node owner)'))
-        self.need_loop=True
+        base.CommandBase.__init__(
+            self,
+            host,
+            "get",
+            use_output=C.OUTPUT_DICT,
+            use_pubsub=True,
+            pubsub_flags={C.NODE},
+            help=_(u"retrieve node subscriptions (for node owner)"),
+        )
+        self.need_loop = True
 
     def add_parser_options(self):
         pass
@@ -274,8 +382,9 @@
         self.host.quit()
 
     def psNodeSubscriptionsGetEb(self, failure_):
-        self.disp(u"can't get node subscriptions: {reason}".format(
-            reason=failure_), error=True)
+        self.disp(
+            u"can't get node subscriptions: {reason}".format(reason=failure_), error=True
+        )
         self.host.quit(C.EXIT_BRIDGE_ERRBACK)
 
     def start(self):
@@ -284,7 +393,8 @@
             self.args.node,
             self.profile,
             callback=self.psNodeSubscriptionsGetCb,
-            errback=self.psNodeSubscriptionsGetEb)
+            errback=self.psNodeSubscriptionsGetEb,
+        )
 
 
 class StoreSubscriptionAction(argparse.Action):
@@ -301,39 +411,53 @@
             try:
                 subscription = values.pop(0)
             except IndexError:
-                subscription = 'subscribed'
+                subscription = "subscribed"
             if subscription not in ALLOWED_SUBSCRIPTIONS_OWNER:
-                parser.error(_(u"subscription must be one of {}").format(u', '.join(ALLOWED_SUBSCRIPTIONS_OWNER)))
+                parser.error(
+                    _(u"subscription must be one of {}").format(
+                        u", ".join(ALLOWED_SUBSCRIPTIONS_OWNER)
+                    )
+                )
             dest_dict[jid_s] = subscription
 
 
 class NodeSubscriptionsSet(base.CommandBase):
-
     def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'set', use_pubsub=True, pubsub_flags={C.NODE}, use_verbose=True, help=_(u'set/modify subscriptions (for node owner)'))
-        self.need_loop=True
+        base.CommandBase.__init__(
+            self,
+            host,
+            "set",
+            use_pubsub=True,
+            pubsub_flags={C.NODE},
+            use_verbose=True,
+            help=_(u"set/modify subscriptions (for node owner)"),
+        )
+        self.need_loop = True
 
     def add_parser_options(self):
         # XXX: we use optional argument syntax for a required one because list of list of 2 elements
         #      (uses to construct dicts) don't work with positional arguments
-        self.parser.add_argument("-S",
-                                 "--subscription",
-                                 dest="subscriptions",
-                                 default={},
-                                 nargs='+',
-                                 metavar=('JID [SUSBSCRIPTION]'),
-                                 required=True,
-                                 type=base.unicode_decoder,
-                                 action=StoreSubscriptionAction,
-                                 help=_(u"entity/subscription couple(s)"))
+        self.parser.add_argument(
+            "-S",
+            "--subscription",
+            dest="subscriptions",
+            default={},
+            nargs="+",
+            metavar=("JID [SUSBSCRIPTION]"),
+            required=True,
+            type=base.unicode_decoder,
+            action=StoreSubscriptionAction,
+            help=_(u"entity/subscription couple(s)"),
+        )
 
     def psNodeSubscriptionsSetCb(self):
         self.disp(_(u"subscriptions have been set"), 1)
         self.host.quit()
 
     def psNodeSubscriptionsSetEb(self, failure_):
-        self.disp(u"can't set node subscriptions: {reason}".format(
-            reason=failure_), error=True)
+        self.disp(
+            u"can't set node subscriptions: {reason}".format(reason=failure_), error=True
+        )
         self.host.quit(C.EXIT_BRIDGE_ERRBACK)
 
     def start(self):
@@ -343,27 +467,40 @@
             self.args.subscriptions,
             self.profile,
             callback=self.psNodeSubscriptionsSetCb,
-            errback=self.psNodeSubscriptionsSetEb)
+            errback=self.psNodeSubscriptionsSetEb,
+        )
 
 
 class NodeSubscriptions(base.CommandBase):
     subcommands = (NodeSubscriptionsGet, NodeSubscriptionsSet)
 
     def __init__(self, host):
-        super(NodeSubscriptions, self).__init__(host, 'subscriptions', use_profile=False, help=_(u'get or modify node subscriptions'))
+        super(NodeSubscriptions, self).__init__(
+            host,
+            "subscriptions",
+            use_profile=False,
+            help=_(u"get or modify node subscriptions"),
+        )
 
 
 class NodeSchemaSet(base.CommandBase):
-
     def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'set', use_pubsub=True, pubsub_flags={C.NODE}, use_verbose=True, help=_(u'set/replace a schema'))
+        base.CommandBase.__init__(
+            self,
+            host,
+            "set",
+            use_pubsub=True,
+            pubsub_flags={C.NODE},
+            use_verbose=True,
+            help=_(u"set/replace a schema"),
+        )
         self.need_loop = True
 
     def add_parser_options(self):
-        self.parser.add_argument('schema', help=_(u"schema to set (must be XML)"))
+        self.parser.add_argument("schema", help=_(u"schema to set (must be XML)"))
 
     def psSchemaSetCb(self):
-        self.disp(_(u'schema has been set'), 1)
+        self.disp(_(u"schema has been set"), 1)
         self.host.quit()
 
     def start(self):
@@ -373,24 +510,36 @@
             self.args.schema,
             self.profile,
             callback=self.psSchemaSetCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't set schema: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
+            errback=partial(
+                self.errback,
+                msg=_(u"can't set schema: {}"),
+                exit_code=C.EXIT_BRIDGE_ERRBACK,
+            ),
+        )
 
 
 class NodeSchemaEdit(base.CommandBase, common.BaseEdit):
-    use_items=False
+    use_items = False
 
     def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'edit', use_pubsub=True, pubsub_flags={C.NODE}, use_draft=True, use_verbose=True, help=_(u'edit a schema'))
+        base.CommandBase.__init__(
+            self,
+            host,
+            "edit",
+            use_pubsub=True,
+            pubsub_flags={C.NODE},
+            use_draft=True,
+            use_verbose=True,
+            help=_(u"edit a schema"),
+        )
         common.BaseEdit.__init__(self, self.host, PUBSUB_SCHEMA_TMP_DIR)
-        self.need_loop=True
+        self.need_loop = True
 
     def add_parser_options(self):
         pass
 
     def psSchemaSetCb(self):
-        self.disp(_(u'schema has been set'), 1)
+        self.disp(_(u"schema has been set"), 1)
         self.host.quit()
 
     def publish(self, schema):
@@ -400,22 +549,30 @@
             schema,
             self.profile,
             callback=self.psSchemaSetCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't set schema: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
+            errback=partial(
+                self.errback,
+                msg=_(u"can't set schema: {}"),
+                exit_code=C.EXIT_BRIDGE_ERRBACK,
+            ),
+        )
 
     def psSchemaGetCb(self, schema):
         try:
             from lxml import etree
         except ImportError:
-            self.disp(u"lxml module must be installed to use edit, please install it with \"pip install lxml\"", error=True)
+            self.disp(
+                u'lxml module must be installed to use edit, please install it with "pip install lxml"',
+                error=True,
+            )
             self.host.quit(1)
         content_file_obj, content_file_path = self.getTmpFile()
         schema = schema.strip()
         if schema:
             parser = etree.XMLParser(remove_blank_text=True)
             schema_elt = etree.fromstring(schema, parser)
-            content_file_obj.write(etree.tostring(schema_elt, encoding="utf-8", pretty_print=True))
+            content_file_obj.write(
+                etree.tostring(schema_elt, encoding="utf-8", pretty_print=True)
+            )
             content_file_obj.seek(0)
         self.runEditor("pubsub_schema_editor_args", content_file_path, content_file_obj)
 
@@ -425,23 +582,34 @@
             self.args.node,
             self.profile,
             callback=self.psSchemaGetCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't edit schema: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
+            errback=partial(
+                self.errback,
+                msg=_(u"can't edit schema: {}"),
+                exit_code=C.EXIT_BRIDGE_ERRBACK,
+            ),
+        )
 
 
 class NodeSchemaGet(base.CommandBase):
-
     def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'get', use_output=C.OUTPUT_XML, use_pubsub=True, pubsub_flags={C.NODE}, use_verbose=True, help=_(u'get schema'))
-        self.need_loop=True
+        base.CommandBase.__init__(
+            self,
+            host,
+            "get",
+            use_output=C.OUTPUT_XML,
+            use_pubsub=True,
+            pubsub_flags={C.NODE},
+            use_verbose=True,
+            help=_(u"get schema"),
+        )
+        self.need_loop = True
 
     def add_parser_options(self):
         pass
 
     def psSchemaGetCb(self, schema):
         if not schema:
-            self.disp(_(u'no schema found'), 1)
+            self.disp(_(u"no schema found"), 1)
             self.host.quit(1)
         self.output(schema)
         self.host.quit()
@@ -452,33 +620,60 @@
             self.args.node,
             self.profile,
             callback=self.psSchemaGetCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't get schema: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
+            errback=partial(
+                self.errback,
+                msg=_(u"can't get schema: {}"),
+                exit_code=C.EXIT_BRIDGE_ERRBACK,
+            ),
+        )
 
 
 class NodeSchema(base.CommandBase):
     subcommands = (NodeSchemaSet, NodeSchemaEdit, NodeSchemaGet)
 
     def __init__(self, host):
-        super(NodeSchema, self).__init__(host, 'schema', use_profile=False, help=_(u"data schema manipulation"))
+        super(NodeSchema, self).__init__(
+            host, "schema", use_profile=False, help=_(u"data schema manipulation")
+        )
 
 
 class Node(base.CommandBase):
-    subcommands = (NodeInfo, NodeCreate, NodeDelete, NodeSet, NodeAffiliations, NodeSubscriptions, NodeSchema)
+    subcommands = (
+        NodeInfo,
+        NodeCreate,
+        NodeDelete,
+        NodeSet,
+        NodeAffiliations,
+        NodeSubscriptions,
+        NodeSchema,
+    )
 
     def __init__(self, host):
-        super(Node, self).__init__(host, 'node', use_profile=False, help=_('node handling'))
+        super(Node, self).__init__(
+            host, "node", use_profile=False, help=_("node handling")
+        )
 
 
 class Set(base.CommandBase):
-
     def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'set', use_pubsub=True, pubsub_flags={C.NODE}, help=_(u'publish a new item or update an existing one'))
-        self.need_loop=True
+        base.CommandBase.__init__(
+            self,
+            host,
+            "set",
+            use_pubsub=True,
+            pubsub_flags={C.NODE},
+            help=_(u"publish a new item or update an existing one"),
+        )
+        self.need_loop = True
 
     def add_parser_options(self):
-        self.parser.add_argument("item", type=base.unicode_decoder, nargs='?', default=u'', help=_(u"id, URL of the item to update, keyword, or nothing for new item"))
+        self.parser.add_argument(
+            "item",
+            type=base.unicode_decoder,
+            nargs="?",
+            default=u"",
+            help=_(u"id, URL of the item to update, keyword, or nothing for new item"),
+        )
 
     def psItemsSendCb(self, published_id):
         if published_id:
@@ -491,50 +686,71 @@
         try:
             from lxml import etree
         except ImportError:
-            self.disp(u"lxml module must be installed to use edit, please install it with \"pip install lxml\"", error=True)
+            self.disp(
+                u'lxml module must be installed to use edit, please install it with "pip install lxml"',
+                error=True,
+            )
             self.host.quit(1)
         try:
             element = etree.parse(sys.stdin).getroot()
         except Exception as e:
-            self.parser.error(_(u"Can't parse the payload XML in input: {msg}").format(msg=e))
-        if element.tag in ('item', '{http://jabber.org/protocol/pubsub}item'):
+            self.parser.error(
+                _(u"Can't parse the payload XML in input: {msg}").format(msg=e)
+            )
+        if element.tag in ("item", "{http://jabber.org/protocol/pubsub}item"):
             if len(element) > 1:
-                self.parser.error(_(u"<item> can only have one child element (the payload)"))
+                self.parser.error(
+                    _(u"<item> can only have one child element (the payload)")
+                )
             element = element[0]
-        payload = etree.tostring(element, encoding='unicode')
+        payload = etree.tostring(element, encoding="unicode")
 
-        self.host.bridge.psItemSend(self.args.service,
-                                    self.args.node,
-                                    payload,
-                                    self.args.item,
-                                    {},
-                                    self.profile,
-                                    callback=self.psItemsSendCb,
-                                    errback=partial(self.errback,
-                                                    msg=_(u"can't send item: {}"),
-                                                    exit_code=C.EXIT_BRIDGE_ERRBACK))
+        self.host.bridge.psItemSend(
+            self.args.service,
+            self.args.node,
+            payload,
+            self.args.item,
+            {},
+            self.profile,
+            callback=self.psItemsSendCb,
+            errback=partial(
+                self.errback,
+                msg=_(u"can't send item: {}"),
+                exit_code=C.EXIT_BRIDGE_ERRBACK,
+            ),
+        )
 
 
 class Get(base.CommandBase):
-
     def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'get', use_output=C.OUTPUT_LIST_XML, use_pubsub=True, pubsub_flags={C.NODE, C.MULTI_ITEMS}, help=_(u'get pubsub item(s)'))
-        self.need_loop=True
+        base.CommandBase.__init__(
+            self,
+            host,
+            "get",
+            use_output=C.OUTPUT_LIST_XML,
+            use_pubsub=True,
+            pubsub_flags={C.NODE, C.MULTI_ITEMS},
+            help=_(u"get pubsub item(s)"),
+        )
+        self.need_loop = True
 
     def add_parser_options(self):
-        self.parser.add_argument("-S", "--sub-id", type=base.unicode_decoder, default=u'',
-                                 help=_(u"subscription id"))
-        # TODO: a key(s) argument to select keys to display
+        self.parser.add_argument(
+            "-S",
+            "--sub-id",
+            type=base.unicode_decoder,
+            default=u"",
+            help=_(u"subscription id"),
+        )
+        #  TODO: a key(s) argument to select keys to display
         # TODO: add MAM filters
 
-
     def psItemsGetCb(self, ps_result):
         self.output(ps_result[0])
         self.host.quit(C.EXIT_OK)
 
     def psItemsGetEb(self, failure_):
-        self.disp(u"can't get pubsub items: {reason}".format(
-            reason=failure_), error=True)
+        self.disp(u"can't get pubsub items: {reason}".format(reason=failure_), error=True)
         self.host.quit(C.EXIT_BRIDGE_ERRBACK)
 
     def start(self):
@@ -547,27 +763,41 @@
             {},
             self.profile,
             callback=self.psItemsGetCb,
-            errback=self.psItemsGetEb)
+            errback=self.psItemsGetEb,
+        )
+
 
 class Delete(base.CommandBase):
-
     def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'delete', use_pubsub=True, pubsub_flags={C.NODE, C.SINGLE_ITEM}, help=_(u'delete an item'))
-        self.need_loop=True
+        base.CommandBase.__init__(
+            self,
+            host,
+            "delete",
+            use_pubsub=True,
+            pubsub_flags={C.NODE, C.SINGLE_ITEM},
+            help=_(u"delete an item"),
+        )
+        self.need_loop = True
 
     def add_parser_options(self):
-        self.parser.add_argument("-f", "--force", action='store_true', help=_(u"delete without confirmation"))
-        self.parser.add_argument("-N", "--notify", action='store_true', help=_(u"notify deletion"))
+        self.parser.add_argument(
+            "-f", "--force", action="store_true", help=_(u"delete without confirmation")
+        )
+        self.parser.add_argument(
+            "-N", "--notify", action="store_true", help=_(u"notify deletion")
+        )
 
     def psItemsDeleteCb(self):
-        self.disp(_(u'item {item_id} has been deleted').format(item_id=self.args.item))
+        self.disp(_(u"item {item_id} has been deleted").format(item_id=self.args.item))
         self.host.quit(C.EXIT_OK)
 
     def start(self):
         if not self.args.item:
             self.parser.error(_(u"You need to specify an item to delete"))
         if not self.args.force:
-            message = _(u"Are you sure to delete item {item_id} ?").format(item_id=self.args.item)
+            message = _(u"Are you sure to delete item {item_id} ?").format(
+                item_id=self.args.item
+            )
             self.host.confirmOrQuit(message, _(u"item deletion cancelled"))
         self.host.bridge.psRetractItem(
             self.args.service,
@@ -576,16 +806,26 @@
             self.args.notify,
             self.profile,
             callback=self.psItemsDeleteCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't delete item: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
+            errback=partial(
+                self.errback,
+                msg=_(u"can't delete item: {}"),
+                exit_code=C.EXIT_BRIDGE_ERRBACK,
+            ),
+        )
 
 
 class Edit(base.CommandBase, common.BaseEdit):
-
     def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'edit', use_verbose=True, use_pubsub=True,
-                                  pubsub_flags={C.NODE, C.SINGLE_ITEM}, use_draft=True, help=_(u'edit an existing or new pubsub item'))
+        base.CommandBase.__init__(
+            self,
+            host,
+            "edit",
+            use_verbose=True,
+            use_pubsub=True,
+            pubsub_flags={C.NODE, C.SINGLE_ITEM},
+            use_draft=True,
+            help=_(u"edit an existing or new pubsub item"),
+        )
         common.BaseEdit.__init__(self, self.host, PUBSUB_TMP_DIR)
 
     def add_parser_options(self):
@@ -596,7 +836,14 @@
         self.runEditor("pubsub_editor_args", content_file_path, content_file_obj)
 
     def publish(self, content):
-        published_id = self.host.bridge.psItemSend(self.pubsub_service, self.pubsub_node, content, self.pubsub_item or '', {}, self.profile)
+        published_id = self.host.bridge.psItemSend(
+            self.pubsub_service,
+            self.pubsub_node,
+            content,
+            self.pubsub_item or "",
+            {},
+            self.profile,
+        )
         if published_id:
             self.disp(u"Item published at {pub_id}".format(pub_id=published_id))
         else:
@@ -606,38 +853,52 @@
         try:
             from lxml import etree
         except ImportError:
-            self.disp(u"lxml module must be installed to use edit, please install it with \"pip install lxml\"", error=True)
+            self.disp(
+                u'lxml module must be installed to use edit, please install it with "pip install lxml"',
+                error=True,
+            )
             self.host.quit(1)
         items = [item] if item is not None else []
-        item_raw =  self.host.bridge.psItemsGet(service, node, 1, items, "", {}, self.profile)[0][0]
+        item_raw = self.host.bridge.psItemsGet(
+            service, node, 1, items, "", {}, self.profile
+        )[0][0]
         parser = etree.XMLParser(remove_blank_text=True)
         item_elt = etree.fromstring(item_raw, parser)
-        item_id = item_elt.get('id')
+        item_id = item_elt.get("id")
         try:
             payload = item_elt[0]
         except IndexError:
-            self.disp(_(u'Item has not payload'), 1)
-            return  u''
+            self.disp(_(u"Item has not payload"), 1)
+            return u""
         return etree.tostring(payload, encoding="unicode", pretty_print=True), item_id
 
     def start(self):
-        self.pubsub_service, self.pubsub_node, self.pubsub_item, content_file_path, content_file_obj = self.getItemPath()
+        self.pubsub_service, self.pubsub_node, self.pubsub_item, content_file_path, content_file_obj = (
+            self.getItemPath()
+        )
         self.edit(content_file_path, content_file_obj)
 
 
 class Subscribe(base.CommandBase):
-
     def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'subscribe', use_pubsub=True, pubsub_flags={C.NODE}, use_verbose=True, help=_(u'subscribe to a node'))
-        self.need_loop=True
+        base.CommandBase.__init__(
+            self,
+            host,
+            "subscribe",
+            use_pubsub=True,
+            pubsub_flags={C.NODE},
+            use_verbose=True,
+            help=_(u"subscribe to a node"),
+        )
+        self.need_loop = True
 
     def add_parser_options(self):
         pass
 
     def psSubscribeCb(self, sub_id):
-        self.disp(_(u'subscription done'), 1)
+        self.disp(_(u"subscription done"), 1)
         if sub_id:
-            self.disp(_(u'subscription id: {sub_id}').format(sub_id=sub_id))
+            self.disp(_(u"subscription id: {sub_id}").format(sub_id=sub_id))
         self.host.quit()
 
     def start(self):
@@ -647,23 +908,34 @@
             {},
             self.profile,
             callback=self.psSubscribeCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't subscribe to node: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
+            errback=partial(
+                self.errback,
+                msg=_(u"can't subscribe to node: {}"),
+                exit_code=C.EXIT_BRIDGE_ERRBACK,
+            ),
+        )
 
 
 class Unsubscribe(base.CommandBase):
     # TODO: voir pourquoi NodeNotFound sur subscribe juste après unsubscribe
 
     def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'unsubscribe', use_pubsub=True, pubsub_flags={C.NODE}, use_verbose=True, help=_(u'unsubscribe from a node'))
-        self.need_loop=True
+        base.CommandBase.__init__(
+            self,
+            host,
+            "unsubscribe",
+            use_pubsub=True,
+            pubsub_flags={C.NODE},
+            use_verbose=True,
+            help=_(u"unsubscribe from a node"),
+        )
+        self.need_loop = True
 
     def add_parser_options(self):
         pass
 
     def psUnsubscribeCb(self):
-        self.disp(_(u'subscription removed'), 1)
+        self.disp(_(u"subscription removed"), 1)
         self.host.quit()
 
     def start(self):
@@ -672,16 +944,25 @@
             self.args.node,
             self.profile,
             callback=self.psUnsubscribeCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't unsubscribe from node: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
+            errback=partial(
+                self.errback,
+                msg=_(u"can't unsubscribe from node: {}"),
+                exit_code=C.EXIT_BRIDGE_ERRBACK,
+            ),
+        )
 
 
 class Subscriptions(base.CommandBase):
-
     def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'subscriptions', use_output=C.OUTPUT_LIST_DICT, use_pubsub=True, help=_(u'retrieve all subscriptions on a service'))
-        self.need_loop=True
+        base.CommandBase.__init__(
+            self,
+            host,
+            "subscriptions",
+            use_output=C.OUTPUT_LIST_DICT,
+            use_pubsub=True,
+            help=_(u"retrieve all subscriptions on a service"),
+        )
+        self.need_loop = True
 
     def add_parser_options(self):
         pass
@@ -696,16 +977,25 @@
             self.args.node,
             self.profile,
             callback=self.psSubscriptionsGetCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't retrieve subscriptions: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
+            errback=partial(
+                self.errback,
+                msg=_(u"can't retrieve subscriptions: {}"),
+                exit_code=C.EXIT_BRIDGE_ERRBACK,
+            ),
+        )
 
 
 class Affiliations(base.CommandBase):
-
     def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'affiliations', use_output=C.OUTPUT_DICT, use_pubsub=True, help=_(u'retrieve all affiliations on a service'))
-        self.need_loop=True
+        base.CommandBase.__init__(
+            self,
+            host,
+            "affiliations",
+            use_output=C.OUTPUT_DICT,
+            use_pubsub=True,
+            help=_(u"retrieve all affiliations on a service"),
+        )
+        self.need_loop = True
 
     def add_parser_options(self):
         pass
@@ -715,8 +1005,9 @@
         self.host.quit()
 
     def psAffiliationsGetEb(self, failure_):
-        self.disp(u"can't get node affiliations: {reason}".format(
-            reason=failure_), error=True)
+        self.disp(
+            u"can't get node affiliations: {reason}".format(reason=failure_), error=True
+        )
         self.host.quit(C.EXIT_BRIDGE_ERRBACK)
 
     def start(self):
@@ -725,24 +1016,35 @@
             self.args.node,
             self.profile,
             callback=self.psAffiliationsGetCb,
-            errback=self.psAffiliationsGetEb)
+            errback=self.psAffiliationsGetEb,
+        )
 
 
 class Search(base.CommandBase):
     """this command to a search without using MAM, i.e. by checking every items if dound by itself, so it may be heavy in resources both for server and client"""
+
     RE_FLAGS = re.MULTILINE | re.UNICODE
-    EXEC_ACTIONS = (u'exec', u'external')
+    EXEC_ACTIONS = (u"exec", u"external")
 
     def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'search', use_output=C.OUTPUT_XML, use_pubsub=True, pubsub_flags={C.MULTI_ITEMS, C.NO_MAX},
-                                  use_verbose=True, help=_(u'search items corresponding to filters'))
-        self.need_loop=True
+        base.CommandBase.__init__(
+            self,
+            host,
+            "search",
+            use_output=C.OUTPUT_XML,
+            use_pubsub=True,
+            pubsub_flags={C.MULTI_ITEMS, C.NO_MAX},
+            use_verbose=True,
+            help=_(u"search items corresponding to filters"),
+        )
+        self.need_loop = True
 
     @property
     def etree(self):
         """load lxml.etree only if needed"""
         if self._etree is None:
             from lxml import etree
+
             self._etree = etree
         return self._etree
 
@@ -755,74 +1057,155 @@
         return (type_, value)
 
     def add_parser_options(self):
-        self.parser.add_argument("-D", "--max-depth", type=int, default=0, help=_(u"maximum depth of recursion (will search linked nodes if > 0, default: 0)"))
-        self.parser.add_argument("-m", "--max", type=int, default=30, help=_(u"maximum number of items to get per node ({} to get all items, default: 30)".format(C.NO_LIMIT)))
-        self.parser.add_argument("-N", "--namespace", action='append', nargs=2, default=[],
-                                 metavar="NAME NAMESPACE", help=_(u"namespace to use for xpath"))
+        self.parser.add_argument(
+            "-D",
+            "--max-depth",
+            type=int,
+            default=0,
+            help=_(
+                u"maximum depth of recursion (will search linked nodes if > 0, default: 0)"
+            ),
+        )
+        self.parser.add_argument(
+            "-m",
+            "--max",
+            type=int,
+            default=30,
+            help=_(
+                u"maximum number of items to get per node ({} to get all items, default: 30)".format(
+                    C.NO_LIMIT
+                )
+            ),
+        )
+        self.parser.add_argument(
+            "-N",
+            "--namespace",
+            action="append",
+            nargs=2,
+            default=[],
+            metavar="NAME NAMESPACE",
+            help=_(u"namespace to use for xpath"),
+        )
 
         # filters
-        filter_text = partial(self.filter_opt, type_=u'text')
-        filter_re = partial(self.filter_opt, type_=u'regex')
-        filter_xpath = partial(self.filter_opt, type_=u'xpath')
-        filter_python = partial(self.filter_opt, type_=u'python')
-        filters = self.parser.add_argument_group(_(u'filters'), _(u'only items corresponding to following filters will be kept'))
-        filters.add_argument("-t", "--text",
-                             action='append', dest='filters', type=filter_text,
-                             metavar='TEXT',
-                             help=_(u"full text filter, item must contain this string (XML included)"))
-        filters.add_argument("-r", "--regex",
-                             action='append', dest='filters', type=filter_re,
-                             metavar='EXPRESSION',
-                             help=_(u"like --text but using a regular expression"))
-        filters.add_argument("-x", "--xpath",
-                             action='append', dest='filters', type=filter_xpath,
-                             metavar='XPATH',
-                             help=_(u"filter items which has elements matching this xpath"))
-        filters.add_argument("-P", "--python",
-                             action='append', dest='filters', type=filter_python,
-                             metavar='PYTHON_CODE',
-                             help=_(u'Python expression which much return a bool (True to keep item, False to reject it). "item" is raw text item, "item_xml" is lxml\'s etree.Element'))
+        filter_text = partial(self.filter_opt, type_=u"text")
+        filter_re = partial(self.filter_opt, type_=u"regex")
+        filter_xpath = partial(self.filter_opt, type_=u"xpath")
+        filter_python = partial(self.filter_opt, type_=u"python")
+        filters = self.parser.add_argument_group(
+            _(u"filters"),
+            _(u"only items corresponding to following filters will be kept"),
+        )
+        filters.add_argument(
+            "-t",
+            "--text",
+            action="append",
+            dest="filters",
+            type=filter_text,
+            metavar="TEXT",
+            help=_(u"full text filter, item must contain this string (XML included)"),
+        )
+        filters.add_argument(
+            "-r",
+            "--regex",
+            action="append",
+            dest="filters",
+            type=filter_re,
+            metavar="EXPRESSION",
+            help=_(u"like --text but using a regular expression"),
+        )
+        filters.add_argument(
+            "-x",
+            "--xpath",
+            action="append",
+            dest="filters",
+            type=filter_xpath,
+            metavar="XPATH",
+            help=_(u"filter items which has elements matching this xpath"),
+        )
+        filters.add_argument(
+            "-P",
+            "--python",
+            action="append",
+            dest="filters",
+            type=filter_python,
+            metavar="PYTHON_CODE",
+            help=_(
+                u'Python expression which much return a bool (True to keep item, False to reject it). "item" is raw text item, "item_xml" is lxml\'s etree.Element'
+            ),
+        )
 
         # filters flags
-        flag_case = partial(self.filter_flag, type_=u'ignore-case')
-        flag_invert = partial(self.filter_flag, type_=u'invert')
-        flag_dotall = partial(self.filter_flag, type_=u'dotall')
-        flag_matching = partial(self.filter_flag, type_=u'only-matching')
-        flags = self.parser.add_argument_group(_(u'filters flags'), _(u'filters modifiers (change behaviour of following filters)'))
-        flags.add_argument("-C", "--ignore-case",
-                           action='append', dest='filters', type=flag_case,
-                           const=('ignore-case', True), nargs='?',
-                           metavar='BOOLEAN',
-                           help=_(u"(don't) ignore case in following filters (default: case sensitive)"))
-        flags.add_argument("-I", "--invert",
-                           action='append', dest='filters', type=flag_invert,
-                           const=('invert', True), nargs='?',
-                           metavar='BOOLEAN',
-                           help=_(u"(don't) invert effect of following filters (default: don't invert)"))
-        flags.add_argument("-A", "--dot-all",
-                           action='append', dest='filters', type=flag_dotall,
-                           const=('dotall', True), nargs='?',
-                           metavar='BOOLEAN',
-                           help=_(u"(don't) use DOTALL option for regex (default: don't use)"))
-        flags.add_argument("-o", "--only-matching",
-                           action='append', dest='filters', type=flag_matching,
-                           const=('only-matching', True), nargs='?',
-                           metavar='BOOLEAN',
-                           help=_(u"keep only the matching part of the item"))
+        flag_case = partial(self.filter_flag, type_=u"ignore-case")
+        flag_invert = partial(self.filter_flag, type_=u"invert")
+        flag_dotall = partial(self.filter_flag, type_=u"dotall")
+        flag_matching = partial(self.filter_flag, type_=u"only-matching")
+        flags = self.parser.add_argument_group(
+            _(u"filters flags"),
+            _(u"filters modifiers (change behaviour of following filters)"),
+        )
+        flags.add_argument(
+            "-C",
+            "--ignore-case",
+            action="append",
+            dest="filters",
+            type=flag_case,
+            const=("ignore-case", True),
+            nargs="?",
+            metavar="BOOLEAN",
+            help=_(u"(don't) ignore case in following filters (default: case sensitive)"),
+        )
+        flags.add_argument(
+            "-I",
+            "--invert",
+            action="append",
+            dest="filters",
+            type=flag_invert,
+            const=("invert", True),
+            nargs="?",
+            metavar="BOOLEAN",
+            help=_(u"(don't) invert effect of following filters (default: don't invert)"),
+        )
+        flags.add_argument(
+            "-A",
+            "--dot-all",
+            action="append",
+            dest="filters",
+            type=flag_dotall,
+            const=("dotall", True),
+            nargs="?",
+            metavar="BOOLEAN",
+            help=_(u"(don't) use DOTALL option for regex (default: don't use)"),
+        )
+        flags.add_argument(
+            "-o",
+            "--only-matching",
+            action="append",
+            dest="filters",
+            type=flag_matching,
+            const=("only-matching", True),
+            nargs="?",
+            metavar="BOOLEAN",
+            help=_(u"keep only the matching part of the item"),
+        )
 
         # action
-        self.parser.add_argument("action",
-                                 default="print",
-                                 nargs='?',
-                                 choices=('print', 'exec', 'external'),
-                                 help=_(u"action to do on found items (default: print)"))
+        self.parser.add_argument(
+            "action",
+            default="print",
+            nargs="?",
+            choices=("print", "exec", "external"),
+            help=_(u"action to do on found items (default: print)"),
+        )
         self.parser.add_argument("command", nargs=argparse.REMAINDER)
 
     def psItemsGetEb(self, failure_, service, node):
-        self.disp(u"can't get pubsub items at {service} (node: {node}): {reason}".format(
-            service=service,
-            node=node,
-            reason=failure_), error=True)
+        self.disp(
+            u"can't get pubsub items at {service} (node: {node}): {reason}".format(
+                service=service, node=node, reason=failure_
+            ),
+            error=True,
+        )
         self.to_get -= 1
 
     def getItems(self, depth, service, node, items):
@@ -837,8 +1220,8 @@
             {},
             self.profile,
             callback=search,
-            errback=errback
-            )
+            errback=errback,
+        )
         self.to_get += 1
 
     def _checkPubsubURL(self, match, found_nodes):
@@ -848,16 +1231,15 @@
             this list will be filled while xmpp: URIs are discovered
         """
         url = match.group(0)
-        if url.startswith(u'xmpp'):
+        if url.startswith(u"xmpp"):
             try:
                 url_data = uri.parseXMPPUri(url)
             except ValueError:
                 return
-            if url_data[u'type'] == u'pubsub':
-                found_node = {u'service': url_data[u'path'],
-                              u'node': url_data[u'node']}
-                if u'item' in url_data:
-                    found_node[u'item'] = url_data[u'item']
+            if url_data[u"type"] == u"pubsub":
+                found_node = {u"service": url_data[u"path"], u"node": url_data[u"node"]}
+                if u"item" in url_data:
+                    found_node[u"item"] = url_data[u"item"]
                 found_nodes.append(found_node)
 
     def getSubNodes(self, item, depth):
@@ -866,17 +1248,23 @@
         checkURI = partial(self._checkPubsubURL, found_nodes=found_nodes)
         strings.RE_URL.sub(checkURI, item)
         for data in found_nodes:
-            self.getItems(depth+1,
-                          data[u'service'],
-                          data[u'node'],
-                          [data[u'item']] if u'item' in data else []
-                          )
+            self.getItems(
+                depth + 1,
+                data[u"service"],
+                data[u"node"],
+                [data[u"item"]] if u"item" in data else [],
+            )
 
     def parseXml(self, item):
         try:
             return self.etree.fromstring(item)
         except self.etree.XMLSyntaxError:
-            self.disp(_(u"item doesn't looks like XML, you have probably used --only-matching somewhere before and we have no more XML"), error=True)
+            self.disp(
+                _(
+                    u"item doesn't looks like XML, you have probably used --only-matching somewhere before and we have no more XML"
+                ),
+                error=True,
+            )
             self.host.quit(C.EXIT_BAD_ARG)
 
     def filter(self, item):
@@ -897,7 +1285,7 @@
 
             ## filters
 
-            if type_ == u'text':
+            if type_ == u"text":
                 if ignore_case:
                     if value.lower() not in item.lower():
                         keep = False
@@ -907,9 +1295,14 @@
                 if keep and only_matching:
                     # doesn't really make sens to keep a fixed string
                     # so we raise an error
-                    self.host.disp(_(u"--only-matching used with fixed --text string, are you sure?"), error=True)
+                    self.host.disp(
+                        _(
+                            u"--only-matching used with fixed --text string, are you sure?"
+                        ),
+                        error=True,
+                    )
                     self.host.quit(C.EXIT_BAD_ARG)
-            elif type_ == u'regex':
+            elif type_ == u"regex":
                 flags = self.RE_FLAGS
                 if ignore_case:
                     flags |= re.IGNORECASE
@@ -920,29 +1313,29 @@
                 if keep and only_matching:
                     item = match.group()
                     item_xml = None
-            elif type_ == u'xpath':
+            elif type_ == u"xpath":
                 if item_xml is None:
                     item_xml = self.parseXml(item)
                 try:
                     elts = item_xml.xpath(value, namespaces=self.args.namespace)
                 except self.etree.XPathEvalError as e:
-                    self.disp(_(u"can't use xpath: {reason}").format(reason=e), error=True)
+                    self.disp(
+                        _(u"can't use xpath: {reason}").format(reason=e), error=True
+                    )
                     self.host.quit(C.EXIT_BAD_ARG)
                 keep = bool(elts)
                 if keep and only_matching:
                     item_xml = elts[0]
                     try:
-                        item = self.etree.tostring(item_xml, encoding='unicode')
+                        item = self.etree.tostring(item_xml, encoding="unicode")
                     except TypeError:
                         # we have a string only, not an element
                         item = unicode(item_xml)
                         item_xml = None
-            elif type_ == u'python':
+            elif type_ == u"python":
                 if item_xml is None:
                     item_xml = self.parseXml(item)
-                cmd_ns = {u'item': item,
-                          u'item_xml': item_xml
-                         }
+                cmd_ns = {u"item": item, u"item_xml": item_xml}
                 try:
                     keep = eval(value, cmd_ns)
                 except SyntaxError as e:
@@ -951,18 +1344,20 @@
 
             ## flags
 
-            elif type_ == u'ignore-case':
+            elif type_ == u"ignore-case":
                 ignore_case = value
-            elif type_ == u'invert':
+            elif type_ == u"invert":
                 invert = value
-                # we need to continue, else loop would end here
+                #  we need to continue, else loop would end here
                 continue
-            elif type_ == u'dotall':
+            elif type_ == u"dotall":
                 dotall = value
-            elif type_ == u'only-matching':
+            elif type_ == u"only-matching":
                 only_matching = value
             else:
-                raise exceptions.InternalError(_(u"unknown filter type {type}").format(type=type_))
+                raise exceptions.InternalError(
+                    _(u"unknown filter type {type}").format(type=type_)
+                )
 
             if invert:
                 keep = not keep
@@ -977,7 +1372,7 @@
         @param item(unicode): accepted item
         """
         action = self.args.action
-        if action == u'print' or self.host.verbosity > 0:
+        if action == u"print" or self.host.verbosity > 0:
             try:
                 self.output(item)
             except self.etree.XMLSyntaxError:
@@ -986,34 +1381,43 @@
                 self.disp(item)
         if action in self.EXEC_ACTIONS:
             item_elt = self.parseXml(item)
-            if action == u'exec':
-                use = {'service': metadata[u'service'],
-                       'node': metadata[u'node'],
-                       'item': item_elt.get('id'),
-                       'profile': self.profile
-                       }
+            if action == u"exec":
+                use = {
+                    "service": metadata[u"service"],
+                    "node": metadata[u"node"],
+                    "item": item_elt.get("id"),
+                    "profile": self.profile,
+                }
                 # we need to send a copy of self.args.command
                 # else it would be modified
-                parser_args, use_args = arg_tools.get_use_args(self.host,
-                    self.args.command,
-                    use,
-                    verbose=self.host.verbosity > 1
-                    )
+                parser_args, use_args = arg_tools.get_use_args(
+                    self.host, self.args.command, use, verbose=self.host.verbosity > 1
+                )
                 cmd_args = sys.argv[0:1] + parser_args + use_args
             else:
                 cmd_args = self.args.command
 
-
-            self.disp(u'COMMAND: {command}'.format(
-                command = u' '.join([arg_tools.escape(a) for a in cmd_args])), 2)
-            if action == u'exec':
+            self.disp(
+                u"COMMAND: {command}".format(
+                    command=u" ".join([arg_tools.escape(a) for a in cmd_args])
+                ),
+                2,
+            )
+            if action == u"exec":
                 ret = subprocess.call(cmd_args)
             else:
                 p = subprocess.Popen(cmd_args, stdin=subprocess.PIPE)
-                p.communicate(item.encode('utf-8'))
+                p.communicate(item.encode("utf-8"))
                 ret = p.wait()
             if ret != 0:
-                self.disp(A.color(C.A_FAILURE, _(u"executed command failed with exit code {code}").format(code=ret)))
+                self.disp(
+                    A.color(
+                        C.A_FAILURE,
+                        _(u"executed command failed with exit code {code}").format(
+                            code=ret
+                        ),
+                    )
+                )
 
     def search(self, items_data, depth):
         """callback of getItems
@@ -1033,7 +1437,7 @@
                 continue
             self.doItemAction(item, metadata)
 
-        # we check if we got all getItems results
+        #  we check if we got all getItems results
         self.to_get -= 1
         if self.to_get == 0:
             # yes, we can quit
@@ -1043,8 +1447,11 @@
     def start(self):
         if self.args.command:
             if self.args.action not in self.EXEC_ACTIONS:
-                self.parser.error(_(u"Command can only be used with {actions} actions").format(
-                    actions=u', '.join(self.EXEC_ACTIONS)))
+                self.parser.error(
+                    _(u"Command can only be used with {actions} actions").format(
+                        actions=u", ".join(self.EXEC_ACTIONS)
+                    )
+                )
         else:
             if self.args.action in self.EXEC_ACTIONS:
                 self.parser.error(_(u"you need to specify a command to execute"))
@@ -1057,64 +1464,105 @@
         self._etree = None
         if self.args.filters is None:
             self.args.filters = []
-        self.args.namespace = dict(self.args.namespace + [('pubsub', "http://jabber.org/protocol/pubsub")])
+        self.args.namespace = dict(
+            self.args.namespace + [("pubsub", "http://jabber.org/protocol/pubsub")]
+        )
         self.getItems(0, self.args.service, self.args.node, self.args.items)
 
 
 class Uri(base.CommandBase):
-
     def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'uri', use_profile=False, use_pubsub=True, pubsub_flags={C.NODE, C.SINGLE_ITEM}, help=_(u'build URI'))
-        self.need_loop=True
+        base.CommandBase.__init__(
+            self,
+            host,
+            "uri",
+            use_profile=False,
+            use_pubsub=True,
+            pubsub_flags={C.NODE, C.SINGLE_ITEM},
+            help=_(u"build URI"),
+        )
+        self.need_loop = True
 
     def add_parser_options(self):
-        self.parser.add_argument("-p", "--profile", type=base.unicode_decoder, default=C.PROF_KEY_DEFAULT, help=_(u"profile (used when no server is specified)"))
+        self.parser.add_argument(
+            "-p",
+            "--profile",
+            type=base.unicode_decoder,
+            default=C.PROF_KEY_DEFAULT,
+            help=_(u"profile (used when no server is specified)"),
+        )
 
     def display_uri(self, jid_):
         uri_args = {}
         if not self.args.service:
             self.args.service = jid.JID(jid_).bare
 
-        for key in ('node', 'service', 'item'):
+        for key in ("node", "service", "item"):
             value = getattr(self.args, key)
-            if key == 'service':
-                key = 'path'
+            if key == "service":
+                key = "path"
             if value:
                 uri_args[key] = value
-        self.disp(uri.buildXMPPUri(u'pubsub', **uri_args))
+        self.disp(uri.buildXMPPUri(u"pubsub", **uri_args))
         self.host.quit()
 
     def start(self):
         if not self.args.service:
             self.host.bridge.asyncGetParamA(
-                u'JabberID',
-                u'Connection',
+                u"JabberID",
+                u"Connection",
                 profile_key=self.args.profile,
                 callback=self.display_uri,
-                errback=partial(self.errback,
-                                msg=_(u"can't retrieve jid: {}"),
-                                exit_code=C.EXIT_BRIDGE_ERRBACK))
+                errback=partial(
+                    self.errback,
+                    msg=_(u"can't retrieve jid: {}"),
+                    exit_code=C.EXIT_BRIDGE_ERRBACK,
+                ),
+            )
         else:
             self.display_uri(None)
 
 
 class HookCreate(base.CommandBase):
-
     def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'create', use_pubsub=True, pubsub_flags={C.NODE}, help=_(u'create a Pubsub hook'))
-        self.need_loop=True
+        base.CommandBase.__init__(
+            self,
+            host,
+            "create",
+            use_pubsub=True,
+            pubsub_flags={C.NODE},
+            help=_(u"create a Pubsub hook"),
+        )
+        self.need_loop = True
 
     def add_parser_options(self):
-        self.parser.add_argument('-t', '--type', default=u'python', choices=('python', 'python_file', 'python_code'), help=_(u"hook type"))
-        self.parser.add_argument('-P', '--persistent', action='store_true', help=_(u"make hook persistent across restarts"))
-        self.parser.add_argument("hook_arg", type=base.unicode_decoder, help=_(u"argument of the hook (depend of the type)"))
+        self.parser.add_argument(
+            "-t",
+            "--type",
+            default=u"python",
+            choices=("python", "python_file", "python_code"),
+            help=_(u"hook type"),
+        )
+        self.parser.add_argument(
+            "-P",
+            "--persistent",
+            action="store_true",
+            help=_(u"make hook persistent across restarts"),
+        )
+        self.parser.add_argument(
+            "hook_arg",
+            type=base.unicode_decoder,
+            help=_(u"argument of the hook (depend of the type)"),
+        )
 
     @staticmethod
     def checkArgs(self):
-        if self.args.type == u'python_file':
+        if self.args.type == u"python_file":
             self.args.hook_arg = os.path.abspath(self.args.hook_arg)
             if not os.path.isfile(self.args.hook_arg):
-                self.parser.error(_(u"{path} is not a file").format(path=self.args.hook_arg))
+                self.parser.error(
+                    _(u"{path} is not a file").format(path=self.args.hook_arg)
+                )
 
     def start(self):
         self.checkArgs(self)
@@ -1126,24 +1574,49 @@
             self.args.persistent,
             self.profile,
             callback=self.host.quit,
-            errback=partial(self.errback,
-                            msg=_(u"can't create hook: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
+            errback=partial(
+                self.errback,
+                msg=_(u"can't create hook: {}"),
+                exit_code=C.EXIT_BRIDGE_ERRBACK,
+            ),
+        )
 
 
 class HookDelete(base.CommandBase):
-
     def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'delete', use_pubsub=True, pubsub_flags={C.NODE}, help=_(u'delete a Pubsub hook'))
-        self.need_loop=True
+        base.CommandBase.__init__(
+            self,
+            host,
+            "delete",
+            use_pubsub=True,
+            pubsub_flags={C.NODE},
+            help=_(u"delete a Pubsub hook"),
+        )
+        self.need_loop = True
 
     def add_parser_options(self):
-        self.parser.add_argument('-t', '--type', default=u'', choices=('', 'python', 'python_file', 'python_code'), help=_(u"hook type to remove, empty to remove all (default: remove all)"))
-        self.parser.add_argument('-a', '--arg', dest='hook_arg', type=base.unicode_decoder, default=u'', help=_(u"argument of the hook to remove, empty to remove all (default: remove all)"))
+        self.parser.add_argument(
+            "-t",
+            "--type",
+            default=u"",
+            choices=("", "python", "python_file", "python_code"),
+            help=_(u"hook type to remove, empty to remove all (default: remove all)"),
+        )
+        self.parser.add_argument(
+            "-a",
+            "--arg",
+            dest="hook_arg",
+            type=base.unicode_decoder,
+            default=u"",
+            help=_(
+                u"argument of the hook to remove, empty to remove all (default: remove all)"
+            ),
+        )
 
     def psHookRemoveCb(self, nb_deleted):
-        self.disp(_(u'{nb_deleted} hook(s) have been deleted').format(
-            nb_deleted = nb_deleted))
+        self.disp(
+            _(u"{nb_deleted} hook(s) have been deleted").format(nb_deleted=nb_deleted)
+        )
         self.host.quit()
 
     def start(self):
@@ -1155,15 +1628,23 @@
             self.args.hook_arg,
             self.profile,
             callback=self.psHookRemoveCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't delete hook: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
+            errback=partial(
+                self.errback,
+                msg=_(u"can't delete hook: {}"),
+                exit_code=C.EXIT_BRIDGE_ERRBACK,
+            ),
+        )
 
 
 class HookList(base.CommandBase):
-
     def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'list', use_output=C.OUTPUT_LIST_DICT, help=_(u'list hooks of a profile'))
+        base.CommandBase.__init__(
+            self,
+            host,
+            "list",
+            use_output=C.OUTPUT_LIST_DICT,
+            help=_(u"list hooks of a profile"),
+        )
         self.need_loop = True
 
     def add_parser_options(self):
@@ -1171,7 +1652,7 @@
 
     def psHookListCb(self, data):
         if not data:
-            self.disp(_(u'No hook found.'))
+            self.disp(_(u"No hook found."))
         self.output(data)
         self.host.quit()
 
@@ -1179,20 +1660,44 @@
         self.host.bridge.psHookList(
             self.profile,
             callback=self.psHookListCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't list hooks: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
+            errback=partial(
+                self.errback,
+                msg=_(u"can't list hooks: {}"),
+                exit_code=C.EXIT_BRIDGE_ERRBACK,
+            ),
+        )
 
 
 class Hook(base.CommandBase):
     subcommands = (HookCreate, HookDelete, HookList)
 
     def __init__(self, host):
-        super(Hook, self).__init__(host, 'hook', use_profile=False, use_verbose=True, help=_('trigger action on Pubsub notifications'))
+        super(Hook, self).__init__(
+            host,
+            "hook",
+            use_profile=False,
+            use_verbose=True,
+            help=_("trigger action on Pubsub notifications"),
+        )
 
 
 class Pubsub(base.CommandBase):
-    subcommands = (Set, Get, Delete, Edit, Subscribe, Unsubscribe, Subscriptions, Node, Affiliations, Search, Hook, Uri)
+    subcommands = (
+        Set,
+        Get,
+        Delete,
+        Edit,
+        Subscribe,
+        Unsubscribe,
+        Subscriptions,
+        Node,
+        Affiliations,
+        Search,
+        Hook,
+        Uri,
+    )
 
     def __init__(self, host):
-        super(Pubsub, self).__init__(host, 'pubsub', use_profile=False, help=_('PubSub nodes/items management'))
+        super(Pubsub, self).__init__(
+            host, "pubsub", use_profile=False, help=_("PubSub nodes/items management")
+        )
--- a/sat_frontends/jp/cmd_shell.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/jp/cmd_shell.py	Wed Jun 27 20:14:46 2018 +0200
@@ -30,19 +30,22 @@
 import subprocess
 
 __commands__ = ["Shell"]
-INTRO = _(u"""Welcome to {app_name} shell, the Salut à Toi shell !
+INTRO = _(
+    u"""Welcome to {app_name} shell, the Salut à Toi shell !
 
 This enrironment helps you using several {app_name} commands with similar parameters.
 
 To quit, just enter "quit" or press C-d.
 Enter "help" or "?" to know what to do
-""").format(app_name = C.APP_NAME)
+"""
+).format(app_name=C.APP_NAME)
 
 
 class Shell(base.CommandBase, cmd.Cmd):
-
     def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'shell', help=_(u'launch jp in shell (REPL) mode'))
+        base.CommandBase.__init__(
+            self, host, "shell", help=_(u"launch jp in shell (REPL) mode")
+        )
         cmd.Cmd.__init__(self)
 
     def parse_args(self, args):
@@ -51,18 +54,20 @@
 
     def update_path(self):
         self._cur_parser = self.host.parser
-        self.help = u''
+        self.help = u""
         for idx, path_elt in enumerate(self.path):
             try:
                 self._cur_parser = arg_tools.get_cmd_choices(path_elt, self._cur_parser)
             except exceptions.NotFound:
-                self.disp(_(u'bad command path'), error=True)
-                self.path=self.path[:idx]
+                self.disp(_(u"bad command path"), error=True)
+                self.path = self.path[:idx]
                 break
             else:
                 self.help = self._cur_parser
 
-        self.prompt = A.color(C.A_PROMPT_PATH, u'/'.join(self.path)) + A.color(C.A_PROMPT_SUF, u'> ')
+        self.prompt = A.color(C.A_PROMPT_PATH, u"/".join(self.path)) + A.color(
+            C.A_PROMPT_SUF, u"> "
+        )
         try:
             self.actions = arg_tools.get_cmd_choices(parser=self._cur_parser).keys()
         except exceptions.NotFound:
@@ -94,12 +99,15 @@
         # didn't found a nice way to work around it so far
         # Situation should be better when we'll move away from python-dbus
         if self.verbose:
-            self.disp(_(u"COMMAND {external}=> {args}").format(
-                external = _(u'(external) ') if external else u'',
-                args=u' '.join(self.format_args(args))))
+            self.disp(
+                _(u"COMMAND {external}=> {args}").format(
+                    external=_(u"(external) ") if external else u"",
+                    args=u" ".join(self.format_args(args)),
+                )
+            )
         if not external:
             args = sys.argv[0:1] + args
-        ret_code= subprocess.call(args)
+        ret_code = subprocess.call(args)
         # XXX: below is a way to launch the command without creating a new process
         #      may be used when a solution to the aforementioned issue is there
         # try:
@@ -113,7 +121,15 @@
         #     ret_code = 0
 
         if ret_code != 0:
-            self.disp(A.color(C.A_FAILURE, u'command failed with an error code of {err_no}'.format(err_no=ret_code)), error=True)
+            self.disp(
+                A.color(
+                    C.A_FAILURE,
+                    u"command failed with an error code of {err_no}".format(
+                        err_no=ret_code
+                    ),
+                ),
+                error=True,
+            )
         return ret_code
 
     def default(self, args):
@@ -122,19 +138,19 @@
         will launch the command with args on the line
         (i.e. will launch do [args])
         """
-        if args=='EOF':
-            self.do_quit('')
+        if args == "EOF":
+            self.do_quit("")
         self.do_do(args)
 
     def do_help(self, args):
         """show help message"""
         if not args:
-            self.disp(A.color(C.A_HEADER, _(u'Shell commands:')), no_lf=True)
+            self.disp(A.color(C.A_HEADER, _(u"Shell commands:")), no_lf=True)
         super(Shell, self).do_help(args)
         if not args:
-            self.disp(A.color(C.A_HEADER, _(u'Action commands:')))
-            help_list = self._cur_parser.format_help().split('\n\n')
-            print('\n\n'.join(help_list[1 if self.path else 2:]))
+            self.disp(A.color(C.A_HEADER, _(u"Action commands:")))
+            help_list = self._cur_parser.format_help().split("\n\n")
+            print("\n\n".join(help_list[1 if self.path else 2 :]))
 
     def do_debug(self, args):
         """launch internal debugger"""
@@ -149,18 +165,21 @@
         args = self.parse_args(args)
         if args:
             self.verbose = C.bool(args[0])
-        self.disp(_(u'verbose mode is {status}').format(
-            status = _(u'ENABLED') if self.verbose else _(u'DISABLED')))
+        self.disp(
+            _(u"verbose mode is {status}").format(
+                status=_(u"ENABLED") if self.verbose else _(u"DISABLED")
+            )
+        )
 
     def do_cmd(self, args):
         """change command path"""
-        if args == '..':
+        if args == "..":
             self.path = self.path[:-1]
         else:
-            if not args or args[0] == '/':
+            if not args or args[0] == "/":
                 self.path = []
-            args = '/'.join(args.split())
-            for path_elt in args.split('/'):
+            args = "/".join(args.split())
+            for path_elt in args.split("/"):
                 path_elt = path_elt.strip()
                 if not path_elt:
                     continue
@@ -170,7 +189,7 @@
     def do_version(self, args):
         """show current SàT/jp version"""
         try:
-            self.host.run(['--version'])
+            self.host.run(["--version"])
         except SystemExit:
             pass
 
@@ -182,28 +201,30 @@
     def do_do(self, args):
         """lauch a command"""
         args = self.parse_args(args)
-        if (self._not_default_profile and
-            not '-p' in args and
-            not '--profile' in args and
-            not 'profile' in self.use):
+        if (
+            self._not_default_profile
+            and not "-p" in args
+            and not "--profile" in args
+            and not "profile" in self.use
+        ):
             # profile is not specified and we are not using the default profile
             # so we need to add it in arguments to use current user profile
             if self.verbose:
-                self.disp(_(u'arg profile={profile} (logged profile)').format(profile=self.profile))
+                self.disp(
+                    _(u"arg profile={profile} (logged profile)").format(
+                        profile=self.profile
+                    )
+                )
             use = self.use.copy()
-            use['profile'] = self.profile
+            use["profile"] = self.profile
         else:
             use = self.use
 
-
         # args may be modified by use_args
         # to remove subparsers from it
-        parser_args, use_args = arg_tools.get_use_args(self.host,
-                                          args,
-                                          use,
-                                          verbose=self.verbose,
-                                          parser=self._cur_parser
-                                          )
+        parser_args, use_args = arg_tools.get_use_args(
+            self.host, args, use, verbose=self.verbose, parser=self._cur_parser
+        )
         cmd_args = self.path + parser_args + use_args
         self.run_cmd(cmd_args)
 
@@ -212,18 +233,31 @@
         args = self.parse_args(args)
         if not args:
             if not self.use:
-                self.disp(_(u'no argument in USE'))
+                self.disp(_(u"no argument in USE"))
             else:
-                self.disp(_(u'arguments in USE:'))
+                self.disp(_(u"arguments in USE:"))
                 for arg, value in self.use.iteritems():
-                    self.disp(_(A.color(C.A_SUBHEADER, arg, A.RESET, u' = ', arg_tools.escape(value))))
+                    self.disp(
+                        _(
+                            A.color(
+                                C.A_SUBHEADER,
+                                arg,
+                                A.RESET,
+                                u" = ",
+                                arg_tools.escape(value),
+                            )
+                        )
+                    )
         elif len(args) != 2:
-            self.disp(u'bad syntax, please use:\nuse [arg] [value]', error=True)
+            self.disp(u"bad syntax, please use:\nuse [arg] [value]", error=True)
         else:
-            self.use[args[0]] = u' '.join(args[1:])
+            self.use[args[0]] = u" ".join(args[1:])
             if self.verbose:
-                self.disp('set {name} = {value}'.format(
-                    name = args[0], value=arg_tools.escape(args[1])))
+                self.disp(
+                    "set {name} = {value}".format(
+                        name=args[0], value=arg_tools.escape(args[1])
+                    )
+                )
 
     def do_use_clear(self, args):
         """unset one or many argument(s) in USE, or all of them if no arg is specified"""
@@ -235,10 +269,15 @@
                 try:
                     del self.use[arg]
                 except KeyError:
-                    self.disp(A.color(C.A_FAILURE, _(u'argument {name} not found').format(name=arg)), error=True)
+                    self.disp(
+                        A.color(
+                            C.A_FAILURE, _(u"argument {name} not found").format(name=arg)
+                        ),
+                        error=True,
+                    )
                 else:
                     if self.verbose:
-                        self.disp(_(u'argument {name} removed').format(name=arg))
+                        self.disp(_(u"argument {name} removed").format(name=arg))
 
     def do_whoami(self, args):
         u"""print profile currently used"""
@@ -246,7 +285,7 @@
 
     def do_quit(self, args):
         u"""quit the shell"""
-        self.disp(_(u'good bye!'))
+        self.disp(_(u"good bye!"))
         self.host.quit()
 
     def do_exit(self, args):
@@ -261,4 +300,4 @@
         self.use = {}
         self.verbose = False
         self.update_path()
-        self.cmdloop(INTRO.encode('utf-8'))
+        self.cmdloop(INTRO.encode("utf-8"))
--- a/sat_frontends/jp/cmd_ticket.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/jp/cmd_ticket.py	Wed Jun 27 20:14:46 2018 +0200
@@ -28,17 +28,23 @@
 
 __commands__ = ["Ticket"]
 
-FIELDS_MAP = u'mapping'
+FIELDS_MAP = u"mapping"
 
 
 class Get(base.CommandBase):
-
     def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'get', use_verbose=True,
-                                  use_pubsub=True, pubsub_flags={C.MULTI_ITEMS},
-                                  pubsub_defaults = {u'service': _(u'auto'), u'node': _(u'auto')},
-                                  use_output=C.OUTPUT_LIST_XMLUI, help=_(u'get tickets'))
-        self.need_loop=True
+        base.CommandBase.__init__(
+            self,
+            host,
+            "get",
+            use_verbose=True,
+            use_pubsub=True,
+            pubsub_flags={C.MULTI_ITEMS},
+            pubsub_defaults={u"service": _(u"auto"), u"node": _(u"auto")},
+            use_output=C.OUTPUT_LIST_XMLUI,
+            help=_(u"get tickets"),
+        )
+        self.need_loop = True
 
     def add_parser_options(self):
         pass
@@ -53,67 +59,140 @@
             self.args.node,
             self.args.max,
             self.args.items,
-            u'',
+            u"",
             {},
             self.profile,
             callback=self.ticketsGetCb,
-            errback=partial(self.errback,
-                            msg=_(u"can't get tickets: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
+            errback=partial(
+                self.errback,
+                msg=_(u"can't get tickets: {}"),
+                exit_code=C.EXIT_BRIDGE_ERRBACK,
+            ),
+        )
 
     def start(self):
-        common.URIFinder(self, os.getcwd(), 'tickets', self.getTickets, meta_map={})
+        common.URIFinder(self, os.getcwd(), "tickets", self.getTickets, meta_map={})
 
 
 class Import(base.CommandAnswering):
     # TODO: factorize with blog/import
 
     def __init__(self, host):
-        super(Import, self).__init__(host, 'import', use_progress=True, help=_(u'import tickets from external software/dataset'))
-        self.need_loop=True
+        super(Import, self).__init__(
+            host,
+            "import",
+            use_progress=True,
+            help=_(u"import tickets from external software/dataset"),
+        )
+        self.need_loop = True
 
     def add_parser_options(self):
-        self.parser.add_argument("importer", type=base.unicode_decoder, nargs='?', help=_(u"importer name, nothing to display importers list"))
-        self.parser.add_argument('-o', '--option', action='append', nargs=2, default=[], metavar=(u'NAME', u'VALUE'),
-            help=_(u"importer specific options (see importer description)"))
-        self.parser.add_argument('-m', '--map', action='append', nargs=2, default=[], metavar=(u'IMPORTED_FIELD', u'DEST_FIELD'),
-            help=_(u"specified field in import data will be put in dest field (default: use same field name, or ignore if it doesn't exist)"))
-        self.parser.add_argument('-s', '--service', type=base.unicode_decoder, default=u'', metavar=u'PUBSUB_SERVICE',
-            help=_(u"PubSub service where the items must be uploaded (default: server)"))
-        self.parser.add_argument('-n', '--node', type=base.unicode_decoder, default=u'', metavar=u'PUBSUB_NODE',
-            help=_(u"PubSub node where the items must be uploaded (default: tickets' defaults)"))
-        self.parser.add_argument("location", type=base.unicode_decoder, nargs='?',
-            help=_(u"importer data location (see importer description), nothing to show importer description"))
+        self.parser.add_argument(
+            "importer",
+            type=base.unicode_decoder,
+            nargs="?",
+            help=_(u"importer name, nothing to display importers list"),
+        )
+        self.parser.add_argument(
+            "-o",
+            "--option",
+            action="append",
+            nargs=2,
+            default=[],
+            metavar=(u"NAME", u"VALUE"),
+            help=_(u"importer specific options (see importer description)"),
+        )
+        self.parser.add_argument(
+            "-m",
+            "--map",
+            action="append",
+            nargs=2,
+            default=[],
+            metavar=(u"IMPORTED_FIELD", u"DEST_FIELD"),
+            help=_(
+                u"specified field in import data will be put in dest field (default: use same field name, or ignore if it doesn't exist)"
+            ),
+        )
+        self.parser.add_argument(
+            "-s",
+            "--service",
+            type=base.unicode_decoder,
+            default=u"",
+            metavar=u"PUBSUB_SERVICE",
+            help=_(u"PubSub service where the items must be uploaded (default: server)"),
+        )
+        self.parser.add_argument(
+            "-n",
+            "--node",
+            type=base.unicode_decoder,
+            default=u"",
+            metavar=u"PUBSUB_NODE",
+            help=_(
+                u"PubSub node where the items must be uploaded (default: tickets' defaults)"
+            ),
+        )
+        self.parser.add_argument(
+            "location",
+            type=base.unicode_decoder,
+            nargs="?",
+            help=_(
+                u"importer data location (see importer description), nothing to show importer description"
+            ),
+        )
 
     def onProgressStarted(self, metadata):
-        self.disp(_(u'Tickets upload started'),2)
+        self.disp(_(u"Tickets upload started"), 2)
 
     def onProgressFinished(self, metadata):
-        self.disp(_(u'Tickets uploaded successfully'),2)
+        self.disp(_(u"Tickets uploaded successfully"), 2)
 
     def onProgressError(self, error_msg):
-        self.disp(_(u'Error while uploading tickets: {}').format(error_msg),error=True)
+        self.disp(_(u"Error while uploading tickets: {}").format(error_msg), error=True)
 
     def error(self, failure):
-        self.disp(_("Error while trying to upload tickets: {reason}").format(reason=failure), error=True)
+        self.disp(
+            _("Error while trying to upload tickets: {reason}").format(reason=failure),
+            error=True,
+        )
         self.host.quit(1)
 
     def start(self):
         if self.args.location is None:
-            for name in ('option', 'service', 'node'):
+            for name in ("option", "service", "node"):
                 if getattr(self.args, name):
-                    self.parser.error(_(u"{name} argument can't be used without location argument").format(name=name))
+                    self.parser.error(
+                        _(
+                            u"{name} argument can't be used without location argument"
+                        ).format(name=name)
+                    )
             if self.args.importer is None:
-                self.disp(u'\n'.join([u'{}: {}'.format(name, desc) for name, desc in self.host.bridge.ticketsImportList()]))
+                self.disp(
+                    u"\n".join(
+                        [
+                            u"{}: {}".format(name, desc)
+                            for name, desc in self.host.bridge.ticketsImportList()
+                        ]
+                    )
+                )
             else:
                 try:
-                    short_desc, long_desc = self.host.bridge.ticketsImportDesc(self.args.importer)
+                    short_desc, long_desc = self.host.bridge.ticketsImportDesc(
+                        self.args.importer
+                    )
                 except Exception as e:
-                    msg = [l for l in unicode(e).split('\n') if l][-1] # we only keep the last line
+                    msg = [l for l in unicode(e).split("\n") if l][
+                        -1
+                    ]  # we only keep the last line
                     self.disp(msg)
                     self.host.quit(1)
                 else:
-                    self.disp(u"{name}: {short_desc}\n\n{long_desc}".format(name=self.args.importer, short_desc=short_desc, long_desc=long_desc))
+                    self.disp(
+                        u"{name}: {short_desc}\n\n{long_desc}".format(
+                            name=self.args.importer,
+                            short_desc=short_desc,
+                            long_desc=long_desc,
+                        )
+                    )
             self.host.quit()
         else:
             # we have a location, an import is requested
@@ -121,16 +200,32 @@
             fields_map = dict(self.args.map)
             if fields_map:
                 if FIELDS_MAP in options:
-                    self.parser.error(_(u"fields_map must be specified either preencoded in --option or using --map, but not both at the same time"))
+                    self.parser.error(
+                        _(
+                            u"fields_map must be specified either preencoded in --option or using --map, but not both at the same time"
+                        )
+                    )
                 options[FIELDS_MAP] = json.dumps(fields_map)
+
             def gotId(id_):
                 self.progress_id = id_
-            self.host.bridge.ticketsImport(self.args.importer, self.args.location, options, self.args.service, self.args.node, self.profile,
-                callback=gotId, errback=self.error)
+
+            self.host.bridge.ticketsImport(
+                self.args.importer,
+                self.args.location,
+                options,
+                self.args.service,
+                self.args.node,
+                self.profile,
+                callback=gotId,
+                errback=self.error,
+            )
 
 
 class Ticket(base.CommandBase):
     subcommands = (Get, Import)
 
     def __init__(self, host):
-        super(Ticket, self).__init__(host, 'ticket', use_profile=False, help=_('tickets handling'))
+        super(Ticket, self).__init__(
+            host, "ticket", use_profile=False, help=_("tickets handling")
+        )
--- a/sat_frontends/jp/cmd_uri.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/jp/cmd_uri.py	Wed Jun 27 20:14:46 2018 +0200
@@ -27,33 +27,44 @@
 
 
 class Parse(base.CommandBase):
-
     def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'parse', use_profile=False, use_output=C.OUTPUT_DICT, help=_(u'parse URI'))
+        base.CommandBase.__init__(
+            self,
+            host,
+            "parse",
+            use_profile=False,
+            use_output=C.OUTPUT_DICT,
+            help=_(u"parse URI"),
+        )
 
     def add_parser_options(self):
-        self.parser.add_argument("uri", type=base.unicode_decoder, help=_(u"XMPP URI to parse"))
+        self.parser.add_argument(
+            "uri", type=base.unicode_decoder, help=_(u"XMPP URI to parse")
+        )
 
     def start(self):
         self.output(uri.parseXMPPUri(self.args.uri))
 
 
 class Build(base.CommandBase):
-
     def __init__(self, host):
-        base.CommandBase.__init__(self, host, 'build', use_profile=False,  help=_(u'build URI'))
+        base.CommandBase.__init__(
+            self, host, "build", use_profile=False, help=_(u"build URI")
+        )
 
     def add_parser_options(self):
         self.parser.add_argument("type", type=base.unicode_decoder, help=_(u"URI type"))
         self.parser.add_argument("path", type=base.unicode_decoder, help=_(u"URI path"))
-        self.parser.add_argument("-f",
+        self.parser.add_argument(
+            "-f",
             "--field",
             type=base.unicode_decoder,
-            action='append',
+            action="append",
             nargs=2,
-            dest='fields',
+            dest="fields",
             metavar=(u"KEY", u"VALUE"),
-            help=_(u"URI fields"))
+            help=_(u"URI fields"),
+        )
 
     def start(self):
         fields = dict(self.args.fields) if self.args.fields else {}
@@ -64,4 +75,6 @@
     subcommands = (Parse, Build)
 
     def __init__(self, host):
-        super(Uri, self).__init__(host, 'uri', use_profile=False, help=_('XMPP URI parsing/generation'))
+        super(Uri, self).__init__(
+            host, "uri", use_profile=False, help=_("XMPP URI parsing/generation")
+        )
--- a/sat_frontends/jp/common.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/jp/common.py	Wed Jun 27 20:14:46 2018 +0200
@@ -40,36 +40,36 @@
 VIM_SPLIT_ARGS = "-c 'set nospr|vsplit|wincmd w|next|wincmd w'"
 EMACS_SPLIT_ARGS = '--eval "(split-window-horizontally)"'
 EDITOR_ARGS_MAGIC = {
-    'vim': VIM_SPLIT_ARGS + ' {content_file} {metadata_file}',
-    'gvim': VIM_SPLIT_ARGS + ' --nofork {content_file} {metadata_file}',
-    'emacs': EMACS_SPLIT_ARGS + ' {content_file} {metadata_file}',
-    'xemacs': EMACS_SPLIT_ARGS + ' {content_file} {metadata_file}',
-    'nano': ' -F {content_file} {metadata_file}',
-    }
+    "vim": VIM_SPLIT_ARGS + " {content_file} {metadata_file}",
+    "gvim": VIM_SPLIT_ARGS + " --nofork {content_file} {metadata_file}",
+    "emacs": EMACS_SPLIT_ARGS + " {content_file} {metadata_file}",
+    "xemacs": EMACS_SPLIT_ARGS + " {content_file} {metadata_file}",
+    "nano": " -F {content_file} {metadata_file}",
+}
 
 SECURE_UNLINK_MAX = 10
 SECURE_UNLINK_DIR = ".backup"
-METADATA_SUFF = '_metadata.json'
+METADATA_SUFF = "_metadata.json"
 
 
 def ansi_ljust(s, width):
     """ljust method handling ANSI escape codes"""
     cleaned = regex.ansiRemove(s)
-    return s + u' ' * (width - len(cleaned))
+    return s + u" " * (width - len(cleaned))
 
 
 def ansi_center(s, width):
     """ljust method handling ANSI escape codes"""
     cleaned = regex.ansiRemove(s)
     diff = width - len(cleaned)
-    half = diff/2
-    return half * u' ' + s + (half + diff % 2) * u' '
+    half = diff / 2
+    return half * u" " + s + (half + diff % 2) * u" "
 
 
 def ansi_rjust(s, width):
     """ljust method handling ANSI escape codes"""
     cleaned = regex.ansiRemove(s)
-    return u' ' * (width - len(cleaned)) + s
+    return u" " * (width - len(cleaned)) + s
 
 
 def getTmpDir(sat_conf, cat_dir, sub_dir=None):
@@ -83,8 +83,8 @@
         initial str)
     @return (str): path to the dir
     """
-    local_dir = config.getConfig(sat_conf, '', 'local_dir', Exception)
-    path = [local_dir.encode('utf-8'), cat_dir.encode('utf-8')]
+    local_dir = config.getConfig(sat_conf, "", "local_dir", Exception)
+    path = [local_dir.encode("utf-8"), cat_dir.encode("utf-8")]
     if sub_dir is not None:
         path.append(regex.pathEscape(sub_dir))
     return os.path.join(*path)
@@ -102,7 +102,9 @@
         # we split arguments first to avoid escaping issues in file names
         return [a.format(**format_kw) for a in shlex.split(cmd_line)]
     except ValueError as e:
-        host.disp(u"Couldn't parse editor cmd [{cmd}]: {reason}".format(cmd=cmd_line, reason=e))
+        host.disp(
+            u"Couldn't parse editor cmd [{cmd}]: {reason}".format(cmd=cmd_line, reason=e)
+        )
         return []
 
 
@@ -124,7 +126,7 @@
         """
         self.host = host
         self.sat_conf = config.parseMainConf()
-        self.cat_dir_str = cat_dir.encode('utf-8')
+        self.cat_dir_str = cat_dir.encode("utf-8")
         self.use_metadata = use_metadata
 
     def secureUnlink(self, path):
@@ -138,7 +140,12 @@
         if not os.path.isfile(path):
             raise OSError(u"path must link to a regular file")
         if not path.startswith(getTmpDir(self.sat_conf, self.cat_dir_str)):
-            self.disp(u"File {} is not in SàT temporary hierarchy, we do not remove it".format(path.decode('utf-8')), 2)
+            self.disp(
+                u"File {} is not in SàT temporary hierarchy, we do not remove it".format(
+                    path.decode("utf-8")
+                ),
+                2,
+            )
             return
         # we have 2 files per draft with use_metadata, so we double max
         unlink_max = SECURE_UNLINK_MAX * 2 if self.use_metadata else SECURE_UNLINK_MAX
@@ -148,19 +155,29 @@
         filename = os.path.basename(path)
         backup_path = os.path.join(backup_dir, filename)
         # we move file to backup dir
-        self.host.disp(u"Backuping file {src} to {dst}".format(
-            src=path.decode('utf-8'), dst=backup_path.decode('utf-8')), 1)
+        self.host.disp(
+            u"Backuping file {src} to {dst}".format(
+                src=path.decode("utf-8"), dst=backup_path.decode("utf-8")
+            ),
+            1,
+        )
         os.rename(path, backup_path)
         # and if we exceeded the limit, we remove older file
         backup_files = [os.path.join(backup_dir, f) for f in os.listdir(backup_dir)]
         if len(backup_files) > unlink_max:
             backup_files.sort(key=lambda path: os.stat(path).st_mtime)
-            for path in backup_files[:len(backup_files) - unlink_max]:
-                self.host.disp(u"Purging backup file {}".format(path.decode('utf-8')), 2)
+            for path in backup_files[: len(backup_files) - unlink_max]:
+                self.host.disp(u"Purging backup file {}".format(path.decode("utf-8")), 2)
                 os.unlink(path)
 
-    def runEditor(self, editor_args_opt, content_file_path,
-                 content_file_obj, meta_file_path=None, meta_ori=None):
+    def runEditor(
+        self,
+        editor_args_opt,
+        content_file_path,
+        content_file_obj,
+        meta_file_path=None,
+        meta_ori=None,
+    ):
         """run editor to edit content and metadata
 
         @param editor_args_opt(unicode): option in [jp] section in configuration for
@@ -178,24 +195,29 @@
 
         # we calculate hashes to check for modifications
         import hashlib
+
         content_file_obj.seek(0)
         tmp_ori_hash = hashlib.sha1(content_file_obj.read()).digest()
         content_file_obj.close()
 
         # we prepare arguments
-        editor = config.getConfig(self.sat_conf, 'jp', 'editor') or os.getenv('EDITOR', 'vi')
+        editor = config.getConfig(self.sat_conf, "jp", "editor") or os.getenv(
+            "EDITOR", "vi"
+        )
         try:
             # is there custom arguments in sat.conf ?
-            editor_args = config.getConfig(self.sat_conf, 'jp', editor_args_opt, Exception)
+            editor_args = config.getConfig(
+                self.sat_conf, "jp", editor_args_opt, Exception
+            )
         except (NoOptionError, NoSectionError):
             # no, we check if we know the editor and have special arguments
             if self.use_metadata:
-                editor_args = EDITOR_ARGS_MAGIC.get(os.path.basename(editor), '')
+                editor_args = EDITOR_ARGS_MAGIC.get(os.path.basename(editor), "")
             else:
-                editor_args = ''
-        parse_kwargs = {'content_file': content_file_path}
+                editor_args = ""
+        parse_kwargs = {"content_file": content_file_path}
         if self.use_metadata:
-            parse_kwargs['metadata_file'] = meta_file_path
+            parse_kwargs["metadata_file"] = meta_file_path
         args = parse_args(self.host, editor_args, **parse_kwargs)
         if not args:
             args = [content_file_path]
@@ -205,61 +227,86 @@
 
         # edition will now be checked, and data will be sent if it was a success
         if editor_exit != 0:
-            self.disp(u"Editor exited with an error code, so temporary file has not be deleted, and item is not published.\nYou can find temporary file at {path}".format(
-                path=content_file_path), error=True)
+            self.disp(
+                u"Editor exited with an error code, so temporary file has not be deleted, and item is not published.\nYou can find temporary file at {path}".format(
+                    path=content_file_path
+                ),
+                error=True,
+            )
         else:
             # main content
             try:
-                with open(content_file_path, 'rb') as f:
+                with open(content_file_path, "rb") as f:
                     content = f.read()
             except (OSError, IOError):
-                self.disp(u"Can read file at {content_path}, have it been deleted?\nCancelling edition".format(
-                    content_path=content_file_path), error=True)
+                self.disp(
+                    u"Can read file at {content_path}, have it been deleted?\nCancelling edition".format(
+                        content_path=content_file_path
+                    ),
+                    error=True,
+                )
                 self.host.quit(C.EXIT_NOT_FOUND)
 
             # metadata
             if self.use_metadata:
                 try:
-                    with open(meta_file_path, 'rb') as f:
+                    with open(meta_file_path, "rb") as f:
                         metadata = json.load(f)
                 except (OSError, IOError):
-                    self.disp(u"Can read file at {meta_file_path}, have it been deleted?\nCancelling edition".format(
-                        content_path=content_file_path, meta_path=meta_file_path), error=True)
+                    self.disp(
+                        u"Can read file at {meta_file_path}, have it been deleted?\nCancelling edition".format(
+                            content_path=content_file_path, meta_path=meta_file_path
+                        ),
+                        error=True,
+                    )
                     self.host.quit(C.EXIT_NOT_FOUND)
                 except ValueError:
-                    self.disp(u"Can't parse metadata, please check it is correct JSON format. Cancelling edition.\n" +
-                        "You can find tmp file at {content_path} and temporary meta file at {meta_path}.".format(
-                        content_path=content_file_path,
-                        meta_path=meta_file_path), error=True)
+                    self.disp(
+                        u"Can't parse metadata, please check it is correct JSON format. Cancelling edition.\n"
+                        + "You can find tmp file at {content_path} and temporary meta file at {meta_path}.".format(
+                            content_path=content_file_path, meta_path=meta_file_path
+                        ),
+                        error=True,
+                    )
                     self.host.quit(C.EXIT_DATA_ERROR)
 
-            if self.use_metadata and not C.bool(metadata.get('publish', "true")):
-                self.disp(u'Publication blocked by "publish" key in metadata, cancelling edition.\n\n' +
-                    "temporary file path:\t{content_path}\nmetadata file path:\t{meta_path}".format(
-                    content_path=content_file_path, meta_path=meta_file_path), error=True)
+            if self.use_metadata and not C.bool(metadata.get("publish", "true")):
+                self.disp(
+                    u'Publication blocked by "publish" key in metadata, cancelling edition.\n\n'
+                    + "temporary file path:\t{content_path}\nmetadata file path:\t{meta_path}".format(
+                        content_path=content_file_path, meta_path=meta_file_path
+                    ),
+                    error=True,
+                )
                 self.host.quit()
 
             if len(content) == 0:
                 self.disp(u"Content is empty, cancelling the edition")
-                if not content_file_path.startswith(getTmpDir(self.sat_conf, self.cat_dir_str)):
-                    self.disp(u"File are not in SàT temporary hierarchy, we do not remove them", 2)
+                if not content_file_path.startswith(
+                    getTmpDir(self.sat_conf, self.cat_dir_str)
+                ):
+                    self.disp(
+                        u"File are not in SàT temporary hierarchy, we do not remove them",
+                        2,
+                    )
                     self.host.quit()
-                self.disp(u"Deletion of {}".format(content_file_path.decode('utf-8')), 2)
+                self.disp(u"Deletion of {}".format(content_file_path.decode("utf-8")), 2)
                 os.unlink(content_file_path)
                 if self.use_metadata:
-                    self.disp(u"Deletion of {}".format(meta_file_path.decode('utf-8')), 2)
+                    self.disp(u"Deletion of {}".format(meta_file_path.decode("utf-8")), 2)
                     os.unlink(meta_file_path)
                 self.host.quit()
 
             # time to re-check the hash
-            elif (tmp_ori_hash == hashlib.sha1(content).digest() and
-                  (not self.use_metadata or meta_ori == metadata)):
+            elif tmp_ori_hash == hashlib.sha1(content).digest() and (
+                not self.use_metadata or meta_ori == metadata
+            ):
                 self.disp(u"The content has not been modified, cancelling the edition")
                 self.host.quit()
 
             else:
                 # we can now send the item
-                content = content.decode('utf-8-sig') # we use utf-8-sig to avoid BOM
+                content = content.decode("utf-8-sig")  # we use utf-8-sig to avoid BOM
                 try:
                     if self.use_metadata:
                         self.publish(content, metadata)
@@ -267,11 +314,21 @@
                         self.publish(content)
                 except Exception as e:
                     if self.use_metadata:
-                        self.disp(u"Error while sending your item, the temporary files have been kept at {content_path} and {meta_path}: {reason}".format(
-                            content_path=content_file_path, meta_path=meta_file_path, reason=e), error=True)
+                        self.disp(
+                            u"Error while sending your item, the temporary files have been kept at {content_path} and {meta_path}: {reason}".format(
+                                content_path=content_file_path,
+                                meta_path=meta_file_path,
+                                reason=e,
+                            ),
+                            error=True,
+                        )
                     else:
-                        self.disp(u"Error while sending your item, the temporary file has been kept at {content_path}: {reason}".format(
-                            content_path=content_file_path, reason=e), error=True)
+                        self.disp(
+                            u"Error while sending your item, the temporary file has been kept at {content_path}: {reason}".format(
+                                content_path=content_file_path, reason=e
+                            ),
+                            error=True,
+                        )
                     self.host.quit(1)
 
             self.secureUnlink(content_file_path)
@@ -288,23 +345,32 @@
         @param suff (str): suffix to use for the filename
         @return (tuple(file, str)): opened (w+b) file object and file path
         """
-        suff = '.' + self.getTmpSuff()
+        suff = "." + self.getTmpSuff()
         cat_dir_str = self.cat_dir_str
-        tmp_dir = getTmpDir(self.sat_conf, self.cat_dir_str, self.profile.encode('utf-8'))
+        tmp_dir = getTmpDir(self.sat_conf, self.cat_dir_str, self.profile.encode("utf-8"))
         if not os.path.exists(tmp_dir):
             try:
                 os.makedirs(tmp_dir)
             except OSError as e:
-                self.disp(u"Can't create {path} directory: {reason}".format(
-                    path=tmp_dir, reason=e), error=True)
+                self.disp(
+                    u"Can't create {path} directory: {reason}".format(
+                        path=tmp_dir, reason=e
+                    ),
+                    error=True,
+                )
                 self.host.quit(1)
         try:
-            fd, path = tempfile.mkstemp(suffix=suff.encode('utf-8'),
-                prefix=time.strftime(cat_dir_str + '_%Y-%m-%d_%H:%M:%S_'),
-                dir=tmp_dir, text=True)
-            return os.fdopen(fd, 'w+b'), path
+            fd, path = tempfile.mkstemp(
+                suffix=suff.encode("utf-8"),
+                prefix=time.strftime(cat_dir_str + "_%Y-%m-%d_%H:%M:%S_"),
+                dir=tmp_dir,
+                text=True,
+            )
+            return os.fdopen(fd, "w+b"), path
         except OSError as e:
-            self.disp(u"Can't create temporary file: {reason}".format(reason=e), error=True)
+            self.disp(
+                u"Can't create temporary file: {reason}".format(reason=e), error=True
+            )
             self.host.quit(1)
 
     def getCurrentFile(self, profile):
@@ -317,10 +383,17 @@
         # the most recent file corresponding to temp file pattern
         # in tmp_dir, excluding metadata files
         cat_dir_str = self.cat_dir_str
-        tmp_dir = getTmpDir(self.sat_conf, self.cat_dir_str, profile.encode('utf-8'))
-        available = [path for path in glob.glob(os.path.join(tmp_dir, cat_dir_str + '_*')) if not path.endswith(METADATA_SUFF)]
+        tmp_dir = getTmpDir(self.sat_conf, self.cat_dir_str, profile.encode("utf-8"))
+        available = [
+            path
+            for path in glob.glob(os.path.join(tmp_dir, cat_dir_str + "_*"))
+            if not path.endswith(METADATA_SUFF)
+        ]
         if not available:
-            self.disp(u"Could not find any content draft in {path}".format(path=tmp_dir), error=True)
+            self.disp(
+                u"Could not find any content draft in {path}".format(path=tmp_dir),
+                error=True,
+            )
             self.host.quit(1)
         return max(available, key=lambda path: os.stat(path).st_mtime)
 
@@ -330,7 +403,7 @@
 
     def getTmpSuff(self):
         """return suffix used for content file"""
-        return u'xml'
+        return u"xml"
 
     def getItemPath(self):
         """retrieve item path (i.e. service and node) from item argument
@@ -345,8 +418,8 @@
         if self.args.current:
             # user wants to continue current draft
             content_file_path = self.getCurrentFile(self.profile)
-            self.disp(u'Continuing edition of current draft', 2)
-            content_file_obj = open(content_file_path, 'r+b')
+            self.disp(u"Continuing edition of current draft", 2)
+            content_file_obj = open(content_file_path, "r+b")
             # we seek at the end of file in case of an item already exist
             # this will write content of the existing item at the end of the draft.
             # This way no data should be lost.
@@ -354,7 +427,7 @@
         elif self.args.draft_path:
             # there is an existing draft that we use
             content_file_path = os.path.expanduser(self.args.item)
-            content_file_obj = open(content_file_path, 'r+b')
+            content_file_obj = open(content_file_path, "r+b")
             # we seek at the end for the same reason as above
             content_file_obj.seek(0, os.SEEK_END)
         else:
@@ -362,7 +435,7 @@
             content_file_obj, content_file_path = self.getTmpFile()
 
         if item or last_item:
-            self.disp(u'Editing requested published item', 2)
+            self.disp(u"Editing requested published item", 2)
             try:
                 if self.use_metadata:
                     content, metadata, item = self.getItemData(service, node, item)
@@ -370,13 +443,18 @@
                     content, item = self.getItemData(service, node, item)
             except Exception as e:
                 # FIXME: ugly but we have not good may to check errors in bridge
-                if u'item-not-found' in unicode(e):
-                    # item doesn't exist, we create a new one with requested id
+                if u"item-not-found" in unicode(e):
+                    #  item doesn't exist, we create a new one with requested id
                     metadata = None
                     if last_item:
-                        self.disp(_(u'no item found at all, we create a new one'), 2)
+                        self.disp(_(u"no item found at all, we create a new one"), 2)
                     else:
-                        self.disp(_(u'item "{item_id}" not found, we create a new item with this id').format(item_id=item), 2)
+                        self.disp(
+                            _(
+                                u'item "{item_id}" not found, we create a new item with this id'
+                            ).format(item_id=item),
+                            2,
+                        )
                     content_file_obj.seek(0)
                 else:
                     self.disp(u"Error while retrieving item: {}".format(e))
@@ -386,12 +464,14 @@
                 if content_file_obj.tell() != 0:
                     # we already have a draft,
                     # we copy item content after it and add an indicator
-                    content_file_obj.write('\n*****\n')
-                content_file_obj.write(content.encode('utf-8'))
+                    content_file_obj.write("\n*****\n")
+                content_file_obj.write(content.encode("utf-8"))
                 content_file_obj.seek(0)
-                self.disp(_(u'item "{item_id}" found, we edit it').format(item_id=item), 2)
+                self.disp(
+                    _(u'item "{item_id}" found, we edit it').format(item_id=item), 2
+                )
         else:
-            self.disp(u'Editing a new item', 2)
+            self.disp(u"Editing a new item", 2)
             if self.use_metadata:
                 metadata = None
 
@@ -402,7 +482,6 @@
 
 
 class Table(object):
-
     def __init__(self, host, data, headers=None, filters=None, use_buffer=False):
         """
         @param data(iterable[list]): table data
@@ -421,17 +500,17 @@
         """
         self.host = host
         self._buffer = [] if use_buffer else None
-        # headers are columns names/titles, can be None
+        #  headers are columns names/titles, can be None
         self.headers = headers
-        # sizes fof columns without headers,
+        #  sizes fof columns without headers,
         # headers may be larger
         self.sizes = []
-        # rows countains one list per row with columns values
+        #  rows countains one list per row with columns values
         self.rows = []
 
         size = None
         if headers:
-            row_cls = namedtuple('RowData', headers)
+            row_cls = namedtuple("RowData", headers)
         else:
             row_cls = tuple
 
@@ -462,21 +541,21 @@
             if size is None:
                 size = len(new_row)
                 if headers is not None and len(headers) != size:
-                    raise exceptions.DataError(u'headers size is not coherent with rows')
+                    raise exceptions.DataError(u"headers size is not coherent with rows")
             else:
                 if len(new_row) != size:
-                    raise exceptions.DataError(u'rows size is not coherent')
+                    raise exceptions.DataError(u"rows size is not coherent")
             self.rows.append(new_row)
 
         if not data and headers is not None:
-            # the table is empty, we print headers at their lenght
+            #  the table is empty, we print headers at their lenght
             self.sizes = [len(h) for h in headers]
 
     @property
     def string(self):
         if self._buffer is None:
-            raise exceptions.InternalError(u'buffer must be used to get a string')
-        return u'\n'.join(self._buffer)
+            raise exceptions.InternalError(u"buffer must be used to get a string")
+        return u"\n".join(self._buffer)
 
     @staticmethod
     def readDictValues(data, keys, defaults=None):
@@ -510,15 +589,17 @@
         """
         if keys is None and headers is not None:
             # FIXME: keys are not needed with OrderedDict,
-            raise exceptions.DataError(u'You must specify keys order to used headers')
+            raise exceptions.DataError(u"You must specify keys order to used headers")
         if keys is None:
             keys = data[0].keys()
         if headers is None:
             headers = keys
         filters = [filters.get(k) for k in keys]
-        return cls(host, (cls.readDictValues(d, keys, defaults) for d in data), headers, filters)
+        return cls(
+            host, (cls.readDictValues(d, keys, defaults) for d in data), headers, filters
+        )
 
-    def _headers(self, head_sep, headers, sizes, alignment=u'left', style=None):
+    def _headers(self, head_sep, headers, sizes, alignment=u"left", style=None):
         """Render headers
 
         @param head_sep(unicode): sequence to use as separator
@@ -532,14 +613,14 @@
             style = [style]
         for idx, header in enumerate(headers):
             size = sizes[idx]
-            if alignment == u'left':
+            if alignment == u"left":
                 rendered = header[:size].ljust(size)
-            elif alignment == u'center':
+            elif alignment == u"center":
                 rendered = header[:size].center(size)
-            elif alignment == u'right':
+            elif alignment == u"right":
                 rendered = header[:size].rjust(size)
             else:
-                raise exceptions.InternalError(u'bad alignment argument')
+                raise exceptions.InternalError(u"bad alignment argument")
             if style:
                 args = style + [rendered]
                 rendered = A.color(*args)
@@ -553,30 +634,31 @@
         else:
             self.host.disp(data)
 
-    def display(self,
-                head_alignment = u'left',
-                columns_alignment = u'left',
-                head_style = None,
-                show_header=True,
-                show_borders=True,
-                hide_cols=None,
-                col_sep=u' │ ',
-                top_left=u'┌',
-                top=u'─',
-                top_sep=u'─┬─',
-                top_right=u'┐',
-                left=u'│',
-                right=None,
-                head_sep=None,
-                head_line=u'┄',
-                head_line_left=u'├',
-                head_line_sep=u'┄┼┄',
-                head_line_right=u'┤',
-                bottom_left=u'└',
-                bottom=None,
-                bottom_sep=u'─┴─',
-                bottom_right=u'┘',
-                ):
+    def display(
+        self,
+        head_alignment=u"left",
+        columns_alignment=u"left",
+        head_style=None,
+        show_header=True,
+        show_borders=True,
+        hide_cols=None,
+        col_sep=u" │ ",
+        top_left=u"┌",
+        top=u"─",
+        top_sep=u"─┬─",
+        top_right=u"┐",
+        left=u"│",
+        right=None,
+        head_sep=None,
+        head_line=u"┄",
+        head_line_left=u"├",
+        head_line_sep=u"┄┼┄",
+        head_line_right=u"┤",
+        bottom_left=u"└",
+        bottom=None,
+        bottom_sep=u"─┴─",
+        bottom_right=u"┘",
+    ):
         """Print the table
 
         @param show_header(bool): True if header need no be shown
@@ -618,14 +700,12 @@
         if bottom_sep is None:
             bottom_sep = col_sep_size * bottom
         if not show_borders:
-            left = right = head_line_left = head_line_right = u''
+            left = right = head_line_left = head_line_right = u""
         # top border
         if show_borders:
             self._disp(
-                top_left
-                + top_sep.join([top*size for size in sizes])
-                + top_right
-                )
+                top_left + top_sep.join([top * size for size in sizes]) + top_right
+            )
 
         # headers
         if show_header:
@@ -633,42 +713,46 @@
                 left
                 + self._headers(head_sep, headers, sizes, head_alignment, head_style)
                 + right
-                )
+            )
             # header line
             self._disp(
                 head_line_left
-                + head_line_sep.join([head_line*size for size in sizes])
+                + head_line_sep.join([head_line * size for size in sizes])
                 + head_line_right
-                )
+            )
 
         # content
-        if columns_alignment == u'left':
+        if columns_alignment == u"left":
             alignment = lambda idx, s: ansi_ljust(s, sizes[idx])
-        elif columns_alignment == u'center':
+        elif columns_alignment == u"center":
             alignment = lambda idx, s: ansi_center(s, sizes[idx])
-        elif columns_alignment == u'right':
+        elif columns_alignment == u"right":
             alignment = lambda idx, s: ansi_rjust(s, sizes[idx])
         else:
-            raise exceptions.InternalError(u'bad columns alignment argument')
+            raise exceptions.InternalError(u"bad columns alignment argument")
 
         for row in self.rows:
             if hide_cols:
-                row = [v for idx,v in enumerate(row) if idx not in ignore_idx]
-            self._disp(left + col_sep.join([alignment(idx,c) for idx,c in enumerate(row)]) + right)
+                row = [v for idx, v in enumerate(row) if idx not in ignore_idx]
+            self._disp(
+                left
+                + col_sep.join([alignment(idx, c) for idx, c in enumerate(row)])
+                + right
+            )
 
         if show_borders:
             # bottom border
             self._disp(
                 bottom_left
-                + bottom_sep.join([bottom*size for size in sizes])
+                + bottom_sep.join([bottom * size for size in sizes])
                 + bottom_right
-                )
-        # we return self so string can be used after display (table.display().string)
+            )
+        #  we return self so string can be used after display (table.display().string)
         return self
 
     def display_blank(self, **kwargs):
         """Display table without visible borders"""
-        kwargs_ = {'col_sep':u' ', 'head_line_sep':u' ', 'show_borders':False}
+        kwargs_ = {"col_sep": u" ", "head_line_sep": u" ", "show_borders": False}
         kwargs_.update(kwargs)
         return self.display(**kwargs_)
 
@@ -696,12 +780,16 @@
             self.key = key
             self.callback = callback
             self.meta_map = meta_map
-            self.host.bridge.URIFind(path,
-                                     [key],
-                                     callback=self.URIFindCb,
-                                     errback=partial(command.errback,
-                                                     msg=_(u"can't find " + key + u" URI: {}"),
-                                                     exit_code=C.EXIT_BRIDGE_ERRBACK))
+            self.host.bridge.URIFind(
+                path,
+                [key],
+                callback=self.URIFindCb,
+                errback=partial(
+                    command.errback,
+                    msg=_(u"can't find " + key + u" URI: {}"),
+                    exit_code=C.EXIT_BRIDGE_ERRBACK,
+                ),
+            )
         else:
             callback()
 
@@ -723,29 +811,34 @@
             try:
                 values = getattr(self.args, key)
             except AttributeError:
-                raise exceptions.InternalError(u'there is no "{key}" arguments'.format(
-                    key=key))
+                raise exceptions.InternalError(
+                    u'there is no "{key}" arguments'.format(key=key)
+                )
             else:
                 if values is None:
                     values = []
                 values.extend(json.loads(new_values_json))
                 setattr(self.args, dest, values)
 
-
     def URIFindCb(self, uris_data):
         try:
             uri_data = uris_data[self.key]
         except KeyError:
-            self.host.disp(_(u"No {key} URI specified for this project, please specify service and node").format(key=self.key), error=True)
+            self.host.disp(
+                _(
+                    u"No {key} URI specified for this project, please specify service and node"
+                ).format(key=self.key),
+                error=True,
+            )
             self.host.quit(C.EXIT_NOT_FOUND)
         else:
-            uri = uri_data[u'uri']
+            uri = uri_data[u"uri"]
 
-        self.setMetadataList(uri_data, u'labels')
+        self.setMetadataList(uri_data, u"labels")
         parsed_uri = xmpp_uri.parseXMPPUri(uri)
         try:
-            self.args.service = parsed_uri[u'path']
-            self.args.node = parsed_uri[u'node']
+            self.args.service = parsed_uri[u"path"]
+            self.args.node = parsed_uri[u"node"]
         except KeyError:
             self.host.disp(_(u"Invalid URI found: {uri}").format(uri=uri), error=True)
             self.host.quit(C.EXIT_DATA_ERROR)
--- a/sat_frontends/jp/constants.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/jp/constants.py	Wed Jun 27 20:14:46 2018 +0200
@@ -26,26 +26,36 @@
     APP_NAME = u"jp"
     PLUGIN_CMD = u"commands"
     PLUGIN_OUTPUT = u"outputs"
-    OUTPUT_TEXT = u'text' # blob of unicode text
-    OUTPUT_DICT = u'dict' # simple key/value dictionary
-    OUTPUT_LIST = u'list'
-    OUTPUT_LIST_DICT = u'list_dict' # list of dictionaries
-    OUTPUT_DICT_DICT = u'dict_dict' # dict  of nested dictionaries
-    OUTPUT_COMPLEX = u'complex' # complex data (e.g. multi-level dictionary)
-    OUTPUT_XML = u'xml' # XML node (as unicode string)
-    OUTPUT_LIST_XML = u'list_xml' # list of XML nodes (as unicode strings)
-    OUTPUT_XMLUI = u'xmlui' # XMLUI as unicode string
-    OUTPUT_LIST_XMLUI = u'list_xmlui' # list of XMLUI (as unicode strings)
-    OUTPUT_TYPES = (OUTPUT_TEXT, OUTPUT_DICT, OUTPUT_LIST, OUTPUT_LIST_DICT, OUTPUT_DICT_DICT,
-                    OUTPUT_COMPLEX, OUTPUT_XML, OUTPUT_LIST_XML, OUTPUT_XMLUI, OUTPUT_LIST_XMLUI)
+    OUTPUT_TEXT = u"text"  # blob of unicode text
+    OUTPUT_DICT = u"dict"  # simple key/value dictionary
+    OUTPUT_LIST = u"list"
+    OUTPUT_LIST_DICT = u"list_dict"  # list of dictionaries
+    OUTPUT_DICT_DICT = u"dict_dict"  # dict  of nested dictionaries
+    OUTPUT_COMPLEX = u"complex"  # complex data (e.g. multi-level dictionary)
+    OUTPUT_XML = u"xml"  # XML node (as unicode string)
+    OUTPUT_LIST_XML = u"list_xml"  # list of XML nodes (as unicode strings)
+    OUTPUT_XMLUI = u"xmlui"  # XMLUI as unicode string
+    OUTPUT_LIST_XMLUI = u"list_xmlui"  # list of XMLUI (as unicode strings)
+    OUTPUT_TYPES = (
+        OUTPUT_TEXT,
+        OUTPUT_DICT,
+        OUTPUT_LIST,
+        OUTPUT_LIST_DICT,
+        OUTPUT_DICT_DICT,
+        OUTPUT_COMPLEX,
+        OUTPUT_XML,
+        OUTPUT_LIST_XML,
+        OUTPUT_XMLUI,
+        OUTPUT_LIST_XMLUI,
+    )
 
     # Pubsub options flags
-    SERVICE = u'service'  # service required
-    NODE = u'node'  # node required
-    ITEM = u'item'  # item required
-    SINGLE_ITEM = u'single_item'  # only one item is allowed
-    MULTI_ITEMS = u'multi_items'  # multiple items are allowed
-    NO_MAX = u'no_max'  # don't add --max option for multi items
+    SERVICE = u"service"  # service required
+    NODE = u"node"  # node required
+    ITEM = u"item"  # item required
+    SINGLE_ITEM = u"single_item"  # only one item is allowed
+    MULTI_ITEMS = u"multi_items"  # multiple items are allowed
+    NO_MAX = u"no_max"  # don't add --max option for multi items
 
     # ANSI
     A_HEADER = A.BOLD + A.FG_YELLOW
@@ -54,7 +64,7 @@
     A_LEVEL_COLORS = (A_HEADER, A.BOLD + A.FG_BLUE, A.FG_MAGENTA, A.FG_CYAN)
     A_SUCCESS = A.BOLD + A.FG_GREEN
     A_FAILURE = A.BOLD + A.FG_RED
-    # A_PROMPT_* is for shell
+    #  A_PROMPT_* is for shell
     A_PROMPT_PATH = A.BOLD + A.FG_CYAN
     A_PROMPT_SUF = A.BOLD
     # Files
@@ -68,8 +78,10 @@
     EXIT_BRIDGE_ERROR = 3  # can't connect to bridge
     EXIT_BRIDGE_ERRBACK = 4  # something went wrong when calling a bridge method
     EXIT_NOT_FOUND = 16  # an item required by a command was not found
-    EXIT_DATA_ERROR = 17  # data needed for a command is invalid
+    EXIT_DATA_ERROR = 17  #  data needed for a command is invalid
     EXIT_USER_CANCELLED = 20  # user cancelled action
-    EXIT_FILE_NOT_EXE = 126  # a file to be executed was found, but it was not an executable utility (cf. man 1 exit)
+    EXIT_FILE_NOT_EXE = (
+        126
+    )  # a file to be executed was found, but it was not an executable utility (cf. man 1 exit)
     EXIT_CMD_NOT_FOUND = 127  # a utility to be executed was not found (cf. man 1 exit)
     EXIT_SIGNAL_INT = 128  # a command was interrupted by a signal (cf. man 1 exit)
--- a/sat_frontends/jp/output_std.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/jp/output_std.py	Wed Jun 27 20:14:46 2018 +0200
@@ -24,9 +24,9 @@
 import json
 
 __outputs__ = ["Simple", "Json"]
-SIMPLE = u'simple'
-JSON = u'json'
-JSON_RAW = u'json_raw'
+SIMPLE = u"simple"
+JSON = u"json"
+JSON_RAW = u"json_raw"
 
 
 class Simple(object):
@@ -45,27 +45,30 @@
         self.host.disp(unicode(data))
 
     def list(self, data):
-        self.host.disp(u'\n'.join(data))
+        self.host.disp(u"\n".join(data))
 
     def dict(self, data, indent=0, header_color=C.A_HEADER):
         options = self.host.parse_output_options()
-        self.host.check_output_options({u'no-header'}, options)
-        show_header = not u'no-header' in options
+        self.host.check_output_options({u"no-header"}, options)
+        show_header = not u"no-header" in options
         for k, v in data.iteritems():
             if show_header:
-                header = A.color(header_color, k) + u': '
+                header = A.color(header_color, k) + u": "
             else:
-                header = u''
+                header = u""
 
-            self.host.disp((u'{indent}{header}{value}'.format(
-                indent=indent*u' ',
-                header=header,
-                value=v)))
+            self.host.disp(
+                (
+                    u"{indent}{header}{value}".format(
+                        indent=indent * u" ", header=header, value=v
+                    )
+                )
+            )
 
     def list_dict(self, data):
         for idx, datum in enumerate(data):
             if idx:
-                self.host.disp(u'\n')
+                self.host.disp(u"\n")
             self.dict(datum)
 
     def dict_dict(self, data):
--- a/sat_frontends/jp/output_template.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/jp/output_template.py	Wed Jun 27 20:14:46 2018 +0200
@@ -27,8 +27,8 @@
 import os.path
 
 __outputs__ = ["Template"]
-TEMPLATE = u'template'
-OPTIONS = {u'template', u'browser', u'inline-css'}
+TEMPLATE = u"template"
+OPTIONS = {u"template", u"browser", u"inline-css"}
 
 
 class Template(object):
@@ -49,28 +49,29 @@
             data to a dict usable by the template.
         """
         # media_dir is needed for the template
-        self.host.media_dir = self.host.bridge.getConfig('', 'media_dir')
+        self.host.media_dir = self.host.bridge.getConfig("", "media_dir")
         cmd = self.host.command
         try:
             template_path = cmd.TEMPLATE
         except AttributeError:
-            if not 'template' in cmd.args.output_opts:
-                self.host.disp(u'no default template set for this command, '
-                    u'you need to specify a template using --oo template=[path/to/template.html]',
-                    error=True)
+            if not "template" in cmd.args.output_opts:
+                self.host.disp(
+                    u"no default template set for this command, "
+                    u"you need to specify a template using --oo template=[path/to/template.html]",
+                    error=True,
+                )
                 self.host.quit(C.EXIT_BAD_ARG)
 
         options = self.host.parse_output_options()
         self.host.check_output_options(OPTIONS, options)
         self.renderer = template.Renderer(self.host)
         try:
-            template_path = options['template']
+            template_path = options["template"]
         except KeyError:
             # template is not specified, we use default one
             pass
         if template_path is None:
-            self.host.disp(u"Can't parse template, please check its syntax",
-                           error=True)
+            self.host.disp(u"Can't parse template, please check its syntax", error=True)
             self.host.quit(C.EXIT_BAD_ARG)
 
         try:
@@ -80,22 +81,29 @@
         else:
             kwargs = mapping_cb(data)
 
-        css_inline = u'inline-css' in options
+        css_inline = u"inline-css" in options
         rendered = self.renderer.render(template_path, css_inline=css_inline, **kwargs)
 
-        if 'browser' in options:
+        if "browser" in options:
             template_name = os.path.basename(template_path)
             tmp_dir = tempfile.mkdtemp()
-            self.host.disp(_(u"Browser opening requested.\nTemporary files are put in the following directory, "
-                             u"you'll have to delete it yourself once finished viewing: {}").format(tmp_dir))
+            self.host.disp(
+                _(
+                    u"Browser opening requested.\nTemporary files are put in the following directory, "
+                    u"you'll have to delete it yourself once finished viewing: {}"
+                ).format(tmp_dir)
+            )
             tmp_file = os.path.join(tmp_dir, template_name)
-            with open(tmp_file, 'w') as f:
-                f.write(rendered.encode('utf-8'))
+            with open(tmp_file, "w") as f:
+                f.write(rendered.encode("utf-8"))
             theme, theme_root_path = self.renderer.getThemeAndRoot(template_path)
             static_dir = os.path.join(theme_root_path, C.TEMPLATE_STATIC_DIR)
             if os.path.exists(static_dir):
                 import shutil
-                shutil.copytree(static_dir, os.path.join(tmp_dir, theme, C.TEMPLATE_STATIC_DIR))
+
+                shutil.copytree(
+                    static_dir, os.path.join(tmp_dir, theme, C.TEMPLATE_STATIC_DIR)
+                )
             webbrowser.open(tmp_file)
         else:
             self.host.disp(rendered)
--- a/sat_frontends/jp/output_xml.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/jp/output_xml.py	Wed Jun 27 20:14:46 2018 +0200
@@ -23,8 +23,10 @@
 from sat.core.i18n import _
 from lxml import etree
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 import sys
+
 try:
     import pygments
     from pygments.lexers.html import XmlLexer
@@ -34,8 +36,8 @@
 
 
 __outputs__ = ["XML"]
-RAW = u'xml_raw'
-PRETTY = u'xml_pretty'
+RAW = u"xml_raw"
+PRETTY = u"xml_pretty"
 
 
 class XML(object):
@@ -50,18 +52,23 @@
 
     def colorize(self, xml):
         if pygments is None:
-            self.host.disp(_(u'Pygments is not available, syntax highlighting is not possible. Please install if from http://pygments.org or with pip install pygments'), error=True)
+            self.host.disp(
+                _(
+                    u"Pygments is not available, syntax highlighting is not possible. Please install if from http://pygments.org or with pip install pygments"
+                ),
+                error=True,
+            )
             return xml
         if not sys.stdout.isatty():
             return xml
-        lexer = XmlLexer(encoding='utf-8')
-        formatter = TerminalFormatter(bg=u'dark')
+        lexer = XmlLexer(encoding="utf-8")
+        formatter = TerminalFormatter(bg=u"dark")
         return pygments.highlight(xml, lexer, formatter)
 
     def format(self, data, pretty=True):
         parser = etree.XMLParser(remove_blank_text=True)
         tree = etree.fromstring(data, parser)
-        xml = etree.tostring(tree, encoding='unicode', pretty_print=pretty)
+        xml = etree.tostring(tree, encoding="unicode", pretty_print=pretty)
         return self.colorize(xml)
 
     def format_no_pretty(self, data):
@@ -70,13 +77,13 @@
     def pretty(self, data):
         self.host.disp(self.format(data))
 
-    def pretty_list(self, data, separator=u'\n'):
+    def pretty_list(self, data, separator=u"\n"):
         list_pretty = map(self.format, data)
         self.host.disp(separator.join(list_pretty))
 
     def raw(self, data):
         self.host.disp(self.format_no_pretty(data))
 
-    def list_raw(self, data, separator=u'\n'):
+    def list_raw(self, data, separator=u"\n"):
         list_no_pretty = map(self.format_no_pretty, data)
         self.host.disp(separator.join(list_no_pretty))
--- a/sat_frontends/jp/output_xmlui.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/jp/output_xmlui.py	Wed Jun 27 20:14:46 2018 +0200
@@ -22,6 +22,7 @@
 from sat_frontends.jp.constants import Const as C
 from sat_frontends.jp import xmlui_manager
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 
 
@@ -34,12 +35,14 @@
     def __init__(self, host):
         self.host = host
         host.register_output(C.OUTPUT_XMLUI, u"simple", self.xmlui, default=True)
-        host.register_output(C.OUTPUT_LIST_XMLUI, u"simple", self.xmlui_list, default=True)
+        host.register_output(
+            C.OUTPUT_LIST_XMLUI, u"simple", self.xmlui_list, default=True
+        )
 
     def xmlui(self, data):
         xmlui = xmlui_manager.create(self.host, data)
         xmlui.show(values_only=True)
-        self.host.disp(u'')
+        self.host.disp(u"")
 
     def xmlui_list(self, data):
         for d in data:
--- a/sat_frontends/jp/xmlui_manager.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/jp/xmlui_manager.py	Wed Jun 27 20:14:46 2018 +0200
@@ -18,6 +18,7 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from sat_frontends.tools import xmlui as xmlui_manager
 from sat_frontends.jp.constants import Const as C
@@ -27,14 +28,15 @@
 
 # workflow constants
 
-SUBMIT = 'SUBMIT'  # submit form
-
+SUBMIT = "SUBMIT"  # submit form
 
 
 ## Widgets ##
 
+
 class Base(object):
     """Base for Widget and Container"""
+
     type = None
     _root = None
 
@@ -58,7 +60,7 @@
 
 
 class Widget(Base):
-    category = u'widget'
+    category = u"widget"
     enabled = True
 
     @property
@@ -85,16 +87,19 @@
         if value is None:
             value = self.name
         if self.host.verbosity:
-            to_disp = [A.FG_MAGENTA,
-                       u' ' if elems else u'',
-                       u'({})'.format(value), A.RESET]
+            to_disp = [
+                A.FG_MAGENTA,
+                u" " if elems else u"",
+                u"({})".format(value),
+                A.RESET,
+            ]
             if elems is None:
                 self.host.disp(A.color(*to_disp))
             else:
                 elems.extend(to_disp)
 
+
 class ValueWidget(Widget):
-
     def __init__(self, xmlui_parent, value):
         super(ValueWidget, self).__init__(xmlui_parent)
         self.value = value
@@ -105,7 +110,6 @@
 
 
 class InputWidget(ValueWidget):
-
     def __init__(self, xmlui_parent, value, read_only=False):
         super(InputWidget, self).__init__(xmlui_parent, value)
         self.read_only = read_only
@@ -115,7 +119,6 @@
 
 
 class OptionsWidget(Widget):
-
     def __init__(self, xmlui_parent, options, selected, style):
         super(OptionsWidget, self).__init__(xmlui_parent)
         self.options = options
@@ -157,21 +160,20 @@
     def items(self):
         """return suitable items, according to style"""
         no_select = self.no_select
-        for value,label in self.options:
+        for value, label in self.options:
             if no_select or value in self.selected:
-                yield value,label
+                yield value, label
 
     @property
     def inline(self):
-        return u'inline' in self.style
+        return u"inline" in self.style
 
     @property
     def no_select(self):
-        return u'noselect' in self.style
+        return u"noselect" in self.style
 
 
 class EmptyWidget(xmlui_manager.EmptyWidget, Widget):
-
     def __init__(self, _xmlui_parent):
         Widget.__init__(self)
 
@@ -193,7 +195,7 @@
         except AttributeError:
             return None
 
-    def show(self, no_lf=False, ansi=u''):
+    def show(self, no_lf=False, ansi=u""):
         """show label
 
         @param no_lf(bool): same as for [JP.disp]
@@ -212,18 +214,17 @@
             elems = []
             self.verboseName(elems)
             if self.value:
-                elems.append(_(u'(enter: {default})').format(default=self.value))
-            elems.extend([C.A_HEADER, u'> '])
+                elems.append(_(u"(enter: {default})").format(default=self.value))
+            elems.extend([C.A_HEADER, u"> "])
             value = raw_input(A.color(*elems))
             if value:
-                # TODO: empty value should be possible
+                #  TODO: empty value should be possible
                 #       an escape key should be used for default instead of enter with empty value
                 self.value = value
 
 
-
 class JidInputWidget(xmlui_manager.JidInputWidget, StringWidget):
-    type = u'jid_input'
+    type = u"jid_input"
 
 
 class TextBoxWidget(xmlui_manager.TextWidget, StringWidget):
@@ -231,7 +232,7 @@
 
 
 class ListWidget(xmlui_manager.ListWidget, OptionsWidget):
-    type = u'list'
+    type = u"list"
     # TODO: handle flags, notably multi
 
     def show(self):
@@ -248,7 +249,7 @@
         for idx, (value, label) in enumerate(self.options):
             elems = []
             if not self.root.read_only:
-                elems.extend([C.A_SUBHEADER, unicode(idx), A.RESET, u': '])
+                elems.extend([C.A_SUBHEADER, unicode(idx), A.RESET, u": "])
             elems.append(label)
             self.verboseName(elems, value)
             self.disp(A.color(*elems))
@@ -261,45 +262,49 @@
             self.value = self.options[0][0]
             return
 
-        # we ask use to choose an option
+        #  we ask use to choose an option
         choice = None
-        limit_max = len(self.options)-1
-        while choice is None or choice<0 or choice>limit_max:
-            choice = raw_input(A.color(C.A_HEADER, _(u'your choice (0-{max}): ').format(max=limit_max)))
+        limit_max = len(self.options) - 1
+        while choice is None or choice < 0 or choice > limit_max:
+            choice = raw_input(
+                A.color(C.A_HEADER, _(u"your choice (0-{max}): ").format(max=limit_max))
+            )
             try:
                 choice = int(choice)
             except ValueError:
                 choice = None
         self.value = self.options[choice][0]
-        self.disp('')
+        self.disp("")
 
 
 class BoolWidget(xmlui_manager.BoolWidget, InputWidget):
-    type = u'bool'
+    type = u"bool"
 
     def show(self):
-        disp_true = A.color(A.FG_GREEN, u'TRUE')
-        disp_false = A.color(A.FG_RED,u'FALSE')
+        disp_true = A.color(A.FG_GREEN, u"TRUE")
+        disp_false = A.color(A.FG_RED, u"FALSE")
         if self.read_only:
             self.disp(disp_true if self.value else disp_false)
         else:
-            self.disp(A.color(C.A_HEADER, u'0: ', disp_false))
-            self.disp(A.color(C.A_HEADER, u'1: ', disp_true))
+            self.disp(A.color(C.A_HEADER, u"0: ", disp_false))
+            self.disp(A.color(C.A_HEADER, u"1: ", disp_true))
             choice = None
-            while choice not in ('0', '1'):
-                elems = [C.A_HEADER, _(u'your choice (0,1): ')]
+            while choice not in ("0", "1"):
+                elems = [C.A_HEADER, _(u"your choice (0,1): ")]
                 self.verboseName(elems)
                 choice = raw_input(A.color(*elems))
             self.value = bool(int(choice))
-            self.disp('')
+            self.disp("")
 
     def _xmluiGetValue(self):
         return C.boolConst(self.value)
 
+
 ## Containers ##
 
+
 class Container(Base):
-    category = u'container'
+    category = u"container"
 
     def __init__(self, xmlui_parent):
         super(Container, self).__init__(xmlui_parent)
@@ -320,39 +325,43 @@
 
 
 class VerticalContainer(xmlui_manager.VerticalContainer, Container):
-    type = u'vertical'
+    type = u"vertical"
 
 
 class PairsContainer(xmlui_manager.PairsContainer, Container):
-    type = u'pairs'
+    type = u"pairs"
 
 
 class LabelContainer(xmlui_manager.PairsContainer, Container):
-    type = u'label'
+    type = u"label"
 
     def show(self):
         for child in self.children:
             no_lf = False
             # we check linked widget type
             # to see if we want the label on the same line or not
-            if child.type == u'label':
+            if child.type == u"label":
                 for_name = child.for_name
                 if for_name is not None:
                     for_widget = self.root.widgets[for_name]
                     wid_type = for_widget.type
-                    if self.root.values_only or wid_type in ('text', 'string', 'jid_input'):
+                    if self.root.values_only or wid_type in (
+                        "text",
+                        "string",
+                        "jid_input",
+                    ):
                         no_lf = True
-                    elif wid_type == 'bool' and for_widget.read_only:
+                    elif wid_type == "bool" and for_widget.read_only:
                         no_lf = True
                 child.show(no_lf=no_lf, ansi=A.FG_CYAN)
             else:
                 child.show()
 
+
 ## Dialogs ##
 
 
 class Dialog(object):
-
     def __init__(self, xmlui_parent):
         self.xmlui_parent = xmlui_parent
         self.host = self.xmlui_parent.host
@@ -369,7 +378,6 @@
 
 
 class NoteDialog(xmlui_manager.NoteDialog, Dialog):
-
     def show(self):
         # TODO: handle title and level
         self.disp(self.message)
@@ -379,11 +387,11 @@
         xmlui_manager.NoteDialog.__init__(self, _xmlui_parent)
         self.title, self.message, self.level = title, message, level
 
+
 ## Factory ##
 
 
 class WidgetFactory(object):
-
     def __getattr__(self, attr):
         if attr.startswith("create"):
             cls = globals()[attr[6:]]
@@ -398,15 +406,27 @@
     workflow = None
     _submit_cb = None
 
-    def __init__(self, host, parsed_dom, title=None, flags=None, callback=None, ignore=None, whitelist=None, profile=None):
-        xmlui_manager.XMLUIPanel.__init__(self,
-                                          host,
-                                          parsed_dom,
-                                          title = title,
-                                          flags = flags,
-                                          ignore = ignore,
-                                          whitelist = whitelist,
-                                          profile=host.profile)
+    def __init__(
+        self,
+        host,
+        parsed_dom,
+        title=None,
+        flags=None,
+        callback=None,
+        ignore=None,
+        whitelist=None,
+        profile=None,
+    ):
+        xmlui_manager.XMLUIPanel.__init__(
+            self,
+            host,
+            parsed_dom,
+            title=title,
+            flags=flags,
+            ignore=ignore,
+            whitelist=whitelist,
+            profile=host.profile,
+        )
         self.submitted = False
 
     @property
@@ -455,7 +475,7 @@
             elif isinstance(cmd, list):
                 name, value = cmd
                 widget = self.widgets[name]
-                if widget.type == 'bool':
+                if widget.type == "bool":
                     value = C.bool(value)
                 widget.value = value
         self.show()
@@ -465,7 +485,7 @@
         self.onFormSubmitted()
 
     def onFormSubmitted(self, ignore=None):
-        # self.submitted is a Q&D workaround to avoid
+        #  self.submitted is a Q&D workaround to avoid
         # double submit when a workflow is set
         if self.submitted:
             return
@@ -478,8 +498,8 @@
     def _launchActionCb(self, data):
         XMLUIPanel._actions -= 1
         assert XMLUIPanel._actions >= 0
-        if u'xmlui' in data:
-            xmlui_raw = data['xmlui']
+        if u"xmlui" in data:
+            xmlui_raw = data["xmlui"]
             xmlui = xmlui_manager.create(self.host, xmlui_raw)
             xmlui.show()
             if xmlui.submit_id:
@@ -498,13 +518,16 @@
             data,
             self.profile,
             callback=self._launchActionCb,
-            errback=partial(self.command.errback,
-                            msg=_(u"can't launch XMLUI action: {}"),
-                            exit_code=C.EXIT_BRIDGE_ERRBACK))
+            errback=partial(
+                self.command.errback,
+                msg=_(u"can't launch XMLUI action: {}"),
+                exit_code=C.EXIT_BRIDGE_ERRBACK,
+            ),
+        )
 
 
 class XMLUIDialog(xmlui_manager.XMLUIDialog):
-    type = 'dialog'
+    type = "dialog"
     dialog_factory = WidgetFactory()
     read_only = False
 
--- a/sat_frontends/primitivus/chat.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/primitivus/chat.py	Wed Jun 27 20:14:46 2018 +0200
@@ -19,6 +19,7 @@
 
 from sat.core.i18n import _
 from sat.core import log as logging
+
 log = logging.getLogger(__name__)
 import urwid
 from urwid_satext import sat_widgets
@@ -36,8 +37,8 @@
 
 OCCUPANTS_FOOTER = _(u"{} occupants")
 
+
 class MessageWidget(urwid.WidgetWrap):
-
     def __init__(self, mess_data):
         """
         @param mess_data(quick_chat.Message, None): message data
@@ -49,7 +50,11 @@
 
     @property
     def markup(self):
-        return self._generateInfoMarkup() if self.mess_data.type == C.MESS_TYPE_INFO else self._generateMarkup()
+        return (
+            self._generateInfoMarkup()
+            if self.mess_data.type == C.MESS_TYPE_INFO
+            else self._generateMarkup()
+        )
 
     @property
     def info_type(self):
@@ -66,7 +71,7 @@
 
     @message.setter
     def message(self, value):
-        self.mess_data.message = {'':value}
+        self.mess_data.message = {"": value}
         self.redraw()
 
     @property
@@ -98,7 +103,7 @@
         return canvas
 
     def _generateInfoMarkup(self):
-        return ('info_msg', self.message)
+        return ("info_msg", self.message)
 
     def _generateMarkup(self):
         """Generate text markup according to message data and Widget options"""
@@ -108,25 +113,29 @@
 
         # message status
         if d.status is None:
-            markup.append(u' ')
+            markup.append(u" ")
         elif d.status == "delivered":
-            markup.append(('msg_status_received', u'✔'))
+            markup.append(("msg_status_received", u"✔"))
         else:
             log.warning(u"Unknown status: {}".format(d.status))
 
         # timestamp
         if self.parent.show_timestamp:
-            attr = 'msg_mention' if mention else 'date'
+            attr = "msg_mention" if mention else "date"
             markup.append((attr, u"[{}]".format(d.time_text)))
         else:
             if mention:
-                markup.append(('msg_mention', '[*]'))
+                markup.append(("msg_mention", "[*]"))
 
         # nickname
         if self.parent.show_short_nick:
-            markup.append(('my_nick' if d.own_mess else 'other_nick', "**" if d.own_mess else "*"))
+            markup.append(
+                ("my_nick" if d.own_mess else "other_nick", "**" if d.own_mess else "*")
+            )
         else:
-            markup.append(('my_nick' if d.own_mess else 'other_nick', u"[{}] ".format(d.nick or '')))
+            markup.append(
+                ("my_nick" if d.own_mess else "other_nick", u"[{}] ".format(d.nick or ""))
+            )
 
         msg = self.message  # needed to generate self.selected_lang
 
@@ -146,18 +155,20 @@
         """
         self.redraw()
 
+
 @total_ordering
 class OccupantWidget(urwid.WidgetWrap):
-
     def __init__(self, occupant_data):
         self.occupant_data = occupant_data
         occupant_data.widgets.add(self)
         markup = self._generateMarkup()
         text = sat_widgets.ClickableText(markup)
-        urwid.connect_signal(text,
-            'click',
+        urwid.connect_signal(
+            text,
+            "click",
             self.occupant_data.parent._occupantsClicked,
-            user_args=[self.occupant_data])
+            user_args=[self.occupant_data],
+        )
         super(OccupantWidget, self).__init__(text)
 
     def __eq__(self, other):
@@ -206,13 +217,12 @@
         #       should be more intuitive and themable
         o = self.occupant_data
         markup = []
-        markup.append(('info_msg', u'{}{} '.format(
-            o.role[0].upper(),
-            o.affiliation[0].upper(),
-            )))
+        markup.append(
+            ("info_msg", u"{}{} ".format(o.role[0].upper(), o.affiliation[0].upper()))
+        )
         markup.append(o.nick)
         if o.state is not None:
-            markup.append(u' {}'.format(C.CHAT_STATE_ICON[o.state]))
+            markup.append(u" {}".format(C.CHAT_STATE_ICON[o.state]))
         return markup
 
     # events
@@ -221,15 +231,16 @@
 
 
 class OccupantsWidget(urwid.WidgetWrap):
-
     def __init__(self, parent):
         self.parent = parent
         self.occupants_walker = urwid.SimpleListWalker([])
-        self.occupants_footer = urwid.Text('', align='center')
+        self.occupants_footer = urwid.Text("", align="center")
         self.updateFooter()
-        occupants_widget = urwid.Frame(urwid.ListBox(self.occupants_walker), footer=self.occupants_footer)
+        occupants_widget = urwid.Frame(
+            urwid.ListBox(self.occupants_walker), footer=self.occupants_footer
+        )
         super(OccupantsWidget, self).__init__(occupants_widget)
-        occupants_list = sorted(self.parent.occupants.keys(), key=lambda o:o.lower())
+        occupants_list = sorted(self.parent.occupants.keys(), key=lambda o: o.lower())
         for occupant in occupants_list:
             occupant_data = self.parent.occupants[occupant]
             self.occupants_walker.append(OccupantWidget(occupant_data))
@@ -239,12 +250,16 @@
         txt = OCCUPANTS_FOOTER.format(len(self.parent.occupants))
         self.occupants_footer.set_text(txt)
 
-    def getNicks(self, start=u''):
+    def getNicks(self, start=u""):
         """Return nicks of all occupants
 
         @param start(unicode): only return nicknames which start with this text
         """
-        return [w.nick for w in self.occupants_walker if isinstance(w, OccupantWidget) and w.nick.startswith(start)]
+        return [
+            w.nick
+            for w in self.occupants_walker
+            if isinstance(w, OccupantWidget) and w.nick.startswith(start)
+        ]
 
     def addUser(self, occupant_data):
         """add a user to the list"""
@@ -261,14 +276,24 @@
 
 
 class Chat(PrimitivusWidget, quick_chat.QuickChat):
-
-    def __init__(self, host, target, type_=C.CHAT_ONE2ONE, nick=None, occupants=None, subject=None, profiles=None):
-        quick_chat.QuickChat.__init__(self, host, target, type_, nick, occupants, subject, profiles=profiles)
+    def __init__(
+        self,
+        host,
+        target,
+        type_=C.CHAT_ONE2ONE,
+        nick=None,
+        occupants=None,
+        subject=None,
+        profiles=None,
+    ):
+        quick_chat.QuickChat.__init__(
+            self, host, target, type_, nick, occupants, subject, profiles=profiles
+        )
         self.filters = []  # list of filter callbacks to apply
         self.mess_walker = urwid.SimpleListWalker([])
         self.mess_widgets = urwid.ListBox(self.mess_walker)
         self.chat_widget = urwid.Frame(self.mess_widgets)
-        self.chat_colums = urwid.Columns([('weight', 8, self.chat_widget)])
+        self.chat_colums = urwid.Columns([("weight", 8, self.chat_widget)])
         self.pile = urwid.Pile([self.chat_colums])
         PrimitivusWidget.__init__(self, self.pile, self.target)
 
@@ -276,9 +301,11 @@
         if type_ == C.CHAT_GROUP:
             if len(self.chat_colums.contents) == 1:
                 self.occupants_widget = OccupantsWidget(self)
-                self.occupants_panel = sat_widgets.VerticalSeparator(self.occupants_widget)
+                self.occupants_panel = sat_widgets.VerticalSeparator(
+                    self.occupants_widget
+                )
                 self._appendOccupantsPanel()
-                self.host.addListener('presence', self.presenceListener, [profiles])
+                self.host.addListener("presence", self.presenceListener, [profiles])
 
         # focus marker is a separator indicated last visible message before focus was lost
         self.focus_marker = None  # link to current marker
@@ -289,30 +316,32 @@
         self.postInit()
 
     def keypress(self, size, key):
-        if key == a_key['OCCUPANTS_HIDE']:  # user wants to (un)hide the occupants panel
+        if key == a_key["OCCUPANTS_HIDE"]:  # user wants to (un)hide the occupants panel
             if self.type == C.CHAT_GROUP:
                 widgets = [widget for (widget, options) in self.chat_colums.contents]
                 if self.occupants_panel in widgets:
                     self._removeOccupantsPanel()
                 else:
                     self._appendOccupantsPanel()
-        elif key == a_key['TIMESTAMP_HIDE']:  # user wants to (un)hide timestamp
+        elif key == a_key["TIMESTAMP_HIDE"]:  # user wants to (un)hide timestamp
             self.show_timestamp = not self.show_timestamp
             self.redraw()
-        elif key == a_key['SHORT_NICKNAME']:  # user wants to (not) use short nick
+        elif key == a_key["SHORT_NICKNAME"]:  # user wants to (not) use short nick
             self.show_short_nick = not self.show_short_nick
             self.redraw()
-        elif key == a_key['SUBJECT_SWITCH']:  # user wants to (un)hide group's subject or change its apperance
+        elif (
+            key == a_key["SUBJECT_SWITCH"]
+        ):  # user wants to (un)hide group's subject or change its apperance
             if self.subject:
                 self.show_title = (self.show_title + 1) % 3
                 if self.show_title == 0:
-                    self.setSubject(self.subject, 'clip')
+                    self.setSubject(self.subject, "clip")
                 elif self.show_title == 1:
-                    self.setSubject(self.subject, 'space')
+                    self.setSubject(self.subject, "space")
                 elif self.show_title == 2:
                     self.chat_widget.header = None
                 self._invalidate()
-        elif key == a_key['GOTO_BOTTOM']:  # user wants to focus last message
+        elif key == a_key["GOTO_BOTTOM"]:  # user wants to focus last message
             self.mess_widgets.focus_position = len(self.mess_walker) - 1
 
         return super(Chat, self).keypress(size, key)
@@ -326,25 +355,25 @@
             return text
 
         space = text.rfind(" ")
-        start = text[space + 1:]
+        start = text[space + 1 :]
         words = self.occupants_widget.getNicks(start)
         if not words:
             return text
         try:
-            word_idx = words.index(completion_data['last_word']) + 1
+            word_idx = words.index(completion_data["last_word"]) + 1
         except (KeyError, ValueError):
             word_idx = 0
         else:
             if word_idx == len(words):
                 word_idx = 0
-        word = completion_data['last_word'] = words[word_idx]
-        return u"{}{}{}".format(text[:space + 1], word, ': ' if space < 0 else '')
+        word = completion_data["last_word"] = words[word_idx]
+        return u"{}{}{}".format(text[: space + 1], word, ": " if space < 0 else "")
 
     def getMenu(self):
         """Return Menu bar"""
         menu = sat_widgets.Menu(self.host.loop)
         if self.type == C.CHAT_GROUP:
-            self.host.addMenus(menu, C.MENU_ROOM, {'room_jid': self.target.bare})
+            self.host.addMenus(menu, C.MENU_ROOM, {"room_jid": self.target.bare})
             game = _("Game")
             menu.addMenu(game, "Tarot", self.onTarotRequest)
         elif self.type == C.CHAT_ONE2ONE:
@@ -354,7 +383,7 @@
                 full_jid = contact_list.getFullJid(self.target)
             else:
                 full_jid = self.target
-            self.host.addMenus(menu, C.MENU_SINGLE, {'jid': full_jid})
+            self.host.addMenus(menu, C.MENU_SINGLE, {"jid": full_jid})
         return menu
 
     def setFilter(self, args):
@@ -399,7 +428,7 @@
         if message.type != C.MESS_TYPE_INFO:
             return False
         try:
-            info_type = message.extra['info_type']
+            info_type = message.extra["info_type"]
         except KeyError:
             return False
         else:
@@ -429,25 +458,33 @@
                     continue
                 if wid.mess_data.type != C.MESS_TYPE_INFO:
                     break
-                if wid.info_type in quick_chat.ROOM_USER_MOVED and wid.mess_data.nick == message.nick:
+                if (
+                    wid.info_type in quick_chat.ROOM_USER_MOVED
+                    and wid.mess_data.nick == message.nick
+                ):
                     try:
                         count = wid.reentered_count
                     except AttributeError:
                         count = wid.reentered_count = 1
                     nick = wid.mess_data.nick
                     if message.info_type == quick_chat.ROOM_USER_LEFT:
-                        wid.message = _(u"<= {nick} has left the room ({count})").format(nick=nick, count=count)
+                        wid.message = _(u"<= {nick} has left the room ({count})").format(
+                            nick=nick, count=count
+                        )
                     else:
-                        wid.message = _(u"<=> {nick} re-entered the room ({count})") .format(nick=nick, count=count)
-                        wid.reentered_count+=1
+                        wid.message = _(
+                            u"<=> {nick} re-entered the room ({count})"
+                        ).format(nick=nick, count=count)
+                        wid.reentered_count += 1
                     return
 
-        if ((self.host.selected_widget != self or not self.host.x_notify.hasFocus())
-            and self.focus_marker_set is not None):
+        if (
+            self.host.selected_widget != self or not self.host.x_notify.hasFocus()
+        ) and self.focus_marker_set is not None:
             if not self.focus_marker_set and not self._locked and self.mess_walker:
                 if self.focus_marker is not None:
                     self.mess_walker.remove(self.focus_marker)
-                self.focus_marker = urwid.Divider('—')
+                self.focus_marker = urwid.Divider("—")
                 self.mess_walker.append(self.focus_marker)
                 self.focus_marker_set = True
                 self._scrollDown()
@@ -473,20 +510,24 @@
 
             if wid.mess_data.mention:
                 from_jid = wid.mess_data.from_jid
-                msg = _(u'You have been mentioned by {nick} in {room}'.format(
-                    nick=wid.mess_data.nick,
-                    room=self.target,
-                    ))
-                self.host.notify(C.NOTIFY_MENTION, from_jid, msg, widget=self, profile=self.profile)
+                msg = _(
+                    u"You have been mentioned by {nick} in {room}".format(
+                        nick=wid.mess_data.nick, room=self.target
+                    )
+                )
+                self.host.notify(
+                    C.NOTIFY_MENTION, from_jid, msg, widget=self, profile=self.profile
+                )
             elif self.type == C.CHAT_ONE2ONE:
                 from_jid = wid.mess_data.from_jid
-                msg = _(u'{entity} is talking to you'.format(
-                    entity=from_jid,
-                    ))
-                self.host.notify(C.NOTIFY_MESSAGE, from_jid, msg, widget=self, profile=self.profile)
+                msg = _(u"{entity} is talking to you".format(entity=from_jid))
+                self.host.notify(
+                    C.NOTIFY_MESSAGE, from_jid, msg, widget=self, profile=self.profile
+                )
             else:
-                self.host.notify(C.NOTIFY_MESSAGE, self.target, widget=self, profile=self.profile)
-
+                self.host.notify(
+                    C.NOTIFY_MESSAGE, self.target, widget=self, profile=self.profile
+                )
 
     def addUser(self, nick):
         occupant = super(Chat, self).addUser(nick)
@@ -505,11 +546,13 @@
         self.getOrCreatePrivateWidget(occupant.jid)
 
         # now we select the new window
-        for contact_list in self.host.widgets.getWidgets(ContactList, profiles=(self.profile,)):
+        for contact_list in self.host.widgets.getWidgets(
+            ContactList, profiles=(self.profile,)
+        ):
             contact_list.setFocus(occupant.jid, True)
 
     def _appendOccupantsPanel(self):
-        self.chat_colums.contents.append((self.occupants_panel, ('weight', 2, False)))
+        self.chat_colums.contents.append((self.occupants_panel, ("weight", 2, False)))
 
     def _removeOccupantsPanel(self):
         for widget, options in self.chat_colums.contents:
@@ -522,9 +565,9 @@
 
         @param widget (Widget): the game panel
         """
-        assert (len(self.pile.contents) == 1)
-        self.pile.contents.insert(0, (widget, ('weight', 1)))
-        self.pile.contents.insert(1, (urwid.Filler(urwid.Divider('-'), ('fixed', 1))))
+        assert len(self.pile.contents) == 1
+        self.pile.contents.insert(0, (widget, ("weight", 1)))
+        self.pile.contents.insert(1, (urwid.Filler(urwid.Divider("-"), ("fixed", 1))))
         self.host.redraw()
 
     def removeGamePanel(self, widget):
@@ -532,16 +575,19 @@
 
         @param widget (Widget): the game panel
         """
-        assert (len(self.pile.contents) == 3)
+        assert len(self.pile.contents) == 3
         del self.pile.contents[0]
         self.host.redraw()
 
-    def setSubject(self, subject, wrap='space'):
+    def setSubject(self, subject, wrap="space"):
         """Set title for a group chat"""
         quick_chat.QuickChat.setSubject(self, subject)
-        self.subj_wid = urwid.Text(unicode(subject.replace('\n', '|') if wrap == 'clip' else subject),
-                                   align='left' if wrap == 'clip' else 'center', wrap=wrap)
-        self.chat_widget.header = urwid.AttrMap(self.subj_wid, 'title')
+        self.subj_wid = urwid.Text(
+            unicode(subject.replace("\n", "|") if wrap == "clip" else subject),
+            align="left" if wrap == "clip" else "center",
+            wrap=wrap,
+        )
+        self.chat_widget.header = urwid.AttrMap(self.subj_wid, "title")
         self.host.redraw()
 
     ## Messages
@@ -564,11 +610,19 @@
             except AttributeError:
                 pass
 
-    def updateHistory(self, size=C.HISTORY_LIMIT_DEFAULT, filters=None, profile='@NONE@'):
+    def updateHistory(self, size=C.HISTORY_LIMIT_DEFAULT, filters=None, profile="@NONE@"):
         del self.mess_walker[:]
-        if filters and 'search' in filters:
-            self.mess_walker.append(urwid.Text(_(u"Results for searching the globbing pattern: {}").format(filters['search'])))
-            self.mess_walker.append(urwid.Text(_(u"Type ':history <lines>' to reset the chat history")))
+        if filters and "search" in filters:
+            self.mess_walker.append(
+                urwid.Text(
+                    _(u"Results for searching the globbing pattern: {}").format(
+                        filters["search"]
+                    )
+                )
+            )
+            self.mess_walker.append(
+                urwid.Text(_(u"Type ':history <lines>' to reset the chat history"))
+            )
         super(Chat, self).updateHistory(size, filters, profile)
 
     def _onHistoryPrinted(self):
@@ -577,7 +631,9 @@
         super(Chat, self)._onHistoryPrinted()
 
     def onPrivateCreated(self, widget):
-        self.host.contact_lists[widget.profile].setSpecial(widget.target, C.CONTACT_SPECIAL_GROUP)
+        self.host.contact_lists[widget.profile].setSpecial(
+            widget.target, C.CONTACT_SPECIAL_GROUP
+        )
 
     def onSelected(self):
         self.focus_marker_set = False
@@ -598,17 +654,32 @@
         self.host.redraw()
         if not self.host.x_notify.hasFocus():
             if self.type == C.CHAT_ONE2ONE:
-                self.host.x_notify.sendNotification(_("Primitivus: %s is talking to you") % contact)
+                self.host.x_notify.sendNotification(
+                    _("Primitivus: %s is talking to you") % contact
+                )
             elif self.nick is not None and self.nick.lower() in msg.lower():
-                self.host.x_notify.sendNotification(_("Primitivus: %(user)s mentioned you in room '%(room)s'") % {'user': contact, 'room': self.target})
+                self.host.x_notify.sendNotification(
+                    _("Primitivus: %(user)s mentioned you in room '%(room)s'")
+                    % {"user": contact, "room": self.target}
+                )
 
     # MENU EVENTS #
     def onTarotRequest(self, menu):
         # TODO: move this to plugin_misc_tarot with dynamic menu
         if len(self.occupants) != 4:
-            self.host.showPopUp(sat_widgets.Alert(_("Can't start game"), _("You need to be exactly 4 peoples in the room to start a Tarot game"), ok_cb=self.host.removePopUp))
+            self.host.showPopUp(
+                sat_widgets.Alert(
+                    _("Can't start game"),
+                    _(
+                        "You need to be exactly 4 peoples in the room to start a Tarot game"
+                    ),
+                    ok_cb=self.host.removePopUp,
+                )
+            )
         else:
-            self.host.bridge.tarotGameCreate(self.target, list(self.occupants), self.profile)
+            self.host.bridge.tarotGameCreate(
+                self.target, list(self.occupants), self.profile
+            )
 
     # MISC EVENTS #
 
@@ -616,7 +687,7 @@
         # FIXME: to be checked after refactoring
         super(Chat, self).onDelete()
         if self.type == C.CHAT_GROUP:
-            self.host.removeListener('presence', self.presenceListener)
+            self.host.removeListener("presence", self.presenceListener)
 
     def onChatState(self, from_jid, state, profile):
         super(Chat, self).onChatState(from_jid, state, profile)
@@ -630,12 +701,14 @@
 
     def onSubjectDialog(self, new_subject=None):
         dialog = sat_widgets.InputDialog(
-            _(u'Change title'),
-            _(u'Enter the new title'),
-            default_txt=new_subject if new_subject is not None else self.subject)
-        dialog.setCallback('ok', self._onSubjectDialogCb, dialog)
-        dialog.setCallback('cancel', lambda dummy: self.host.removePopUp(dialog))
+            _(u"Change title"),
+            _(u"Enter the new title"),
+            default_txt=new_subject if new_subject is not None else self.subject,
+        )
+        dialog.setCallback("ok", self._onSubjectDialogCb, dialog)
+        dialog.setCallback("cancel", lambda dummy: self.host.removePopUp(dialog))
         self.host.showPopUp(dialog)
 
+
 quick_widgets.register(quick_chat.QuickChat, Chat)
 quick_widgets.register(quick_games.Tarot, game_tarot.TarotGame)
--- a/sat_frontends/primitivus/config.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/primitivus/config.py	Wed Jun 27 20:14:46 2018 +0200
@@ -38,7 +38,7 @@
     shortcuts = {}
     for name, value in options:
         if name.startswith(C.CONFIG_OPT_KEY_PREFIX.lower()):
-            action = name[len(C.CONFIG_OPT_KEY_PREFIX):].upper()
+            action = name[len(C.CONFIG_OPT_KEY_PREFIX) :].upper()
             shortcut = value
             if not action or not shortcut:
                 raise ValueError("Bad option: {} = {}".format(name, value))
--- a/sat_frontends/primitivus/constants.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/primitivus/constants.py	Wed Jun 27 20:14:46 2018 +0200
@@ -25,73 +25,76 @@
     APP_NAME = "Primitivus"
     SECTION_NAME = APP_NAME.lower()
     PALETTE = [
-               ('title', 'black', 'light gray', 'standout,underline'),
-               ('title_focus', 'white,bold', 'light gray', 'standout,underline'),
-               ('selected', 'default', 'dark red'),
-               ('selected_focus', 'default,bold', 'dark red'),
-               ('default', 'default', 'default'),
-               ('default_focus', 'default,bold', 'default'),
-               ('cl_notifs', 'yellow', 'default'),
-               ('cl_notifs_focus', 'yellow,bold', 'default'),
-               ('cl_mention', 'dark red', 'default'),
-               ('cl_mention_focus', 'dark red,bold', 'default'),
-               # Messages
-               ('date', 'light gray', 'default'),
-               ('my_nick', 'dark red,bold', 'default'),
-               ('other_nick', 'dark cyan,bold', 'default'),
-               ('info_msg', 'yellow', 'default', 'bold'),
-               ('msg_lang', 'dark cyan', 'default'),
-               ('msg_mention', 'dark red, bold', 'default'),
-               ('msg_status_received', 'light green, bold', 'default'),
-
-               ('menubar', 'light gray,bold', 'dark red'),
-               ('menubar_focus', 'light gray,bold', 'dark green'),
-               ('selected_menu', 'light gray,bold', 'dark green'),
-               ('menuitem', 'light gray,bold', 'dark red'),
-               ('menuitem_focus', 'light gray,bold', 'dark green'),
-               ('notifs', 'black,bold', 'yellow'),
-               ('notifs_focus', 'dark red', 'yellow'),
-               ('card_neutral', 'dark gray', 'white', 'standout,underline'),
-               ('card_neutral_selected', 'dark gray', 'dark green', 'standout,underline'),
-               ('card_special', 'brown', 'white', 'standout,underline'),
-               ('card_special_selected', 'brown', 'dark green', 'standout,underline'),
-               ('card_red', 'dark red', 'white', 'standout,underline'),
-               ('card_red_selected', 'dark red', 'dark green', 'standout,underline'),
-               ('card_black', 'black', 'white', 'standout,underline'),
-               ('card_black_selected', 'black', 'dark green', 'standout,underline'),
-               ('directory', 'dark cyan, bold', 'default'),
-               ('directory_focus', 'dark cyan, bold', 'dark green'),
-               ('separator', 'brown', 'default'),
-               ('warning', 'light red', 'default'),
-               ('progress_normal', 'default', 'brown'),
-               ('progress_complete', 'default', 'dark green'),
-               ('show_disconnected', 'dark gray', 'default'),
-               ('show_normal', 'default', 'default'),
-               ('show_normal_focus', 'default, bold', 'default'),
-               ('show_chat', 'dark green', 'default'),
-               ('show_chat_focus', 'dark green, bold', 'default'),
-               ('show_away', 'brown', 'default'),
-               ('show_away_focus', 'brown, bold', 'default'),
-               ('show_dnd', 'dark red', 'default'),
-               ('show_dnd_focus', 'dark red, bold', 'default'),
-               ('show_xa', 'dark red', 'default'),
-               ('show_xa_focus', 'dark red, bold', 'default'),
-               ('resource', 'light blue', 'default'),
-               ('resource_main', 'dark blue', 'default'),
-               ('status', 'yellow', 'default'),
-               ('status_focus', 'yellow, bold', 'default'),
-               ('param_selected','default, bold', 'dark red'),
-               ('table_selected','default, bold', 'default'),
-               ]
-    PRESENCE = {"unavailable": (u'⨯', "show_disconnected"),
-                "": (u'✔', "show_normal"),
-                "chat": (u'✆', "show_chat"),
-                "away": (u'✈', "show_away"),
-                "dnd": (u'✖', "show_dnd"),
-                "xa": (u'☄', "show_xa")
-                }
+        ("title", "black", "light gray", "standout,underline"),
+        ("title_focus", "white,bold", "light gray", "standout,underline"),
+        ("selected", "default", "dark red"),
+        ("selected_focus", "default,bold", "dark red"),
+        ("default", "default", "default"),
+        ("default_focus", "default,bold", "default"),
+        ("cl_notifs", "yellow", "default"),
+        ("cl_notifs_focus", "yellow,bold", "default"),
+        ("cl_mention", "dark red", "default"),
+        ("cl_mention_focus", "dark red,bold", "default"),
+        # Messages
+        ("date", "light gray", "default"),
+        ("my_nick", "dark red,bold", "default"),
+        ("other_nick", "dark cyan,bold", "default"),
+        ("info_msg", "yellow", "default", "bold"),
+        ("msg_lang", "dark cyan", "default"),
+        ("msg_mention", "dark red, bold", "default"),
+        ("msg_status_received", "light green, bold", "default"),
+        ("menubar", "light gray,bold", "dark red"),
+        ("menubar_focus", "light gray,bold", "dark green"),
+        ("selected_menu", "light gray,bold", "dark green"),
+        ("menuitem", "light gray,bold", "dark red"),
+        ("menuitem_focus", "light gray,bold", "dark green"),
+        ("notifs", "black,bold", "yellow"),
+        ("notifs_focus", "dark red", "yellow"),
+        ("card_neutral", "dark gray", "white", "standout,underline"),
+        ("card_neutral_selected", "dark gray", "dark green", "standout,underline"),
+        ("card_special", "brown", "white", "standout,underline"),
+        ("card_special_selected", "brown", "dark green", "standout,underline"),
+        ("card_red", "dark red", "white", "standout,underline"),
+        ("card_red_selected", "dark red", "dark green", "standout,underline"),
+        ("card_black", "black", "white", "standout,underline"),
+        ("card_black_selected", "black", "dark green", "standout,underline"),
+        ("directory", "dark cyan, bold", "default"),
+        ("directory_focus", "dark cyan, bold", "dark green"),
+        ("separator", "brown", "default"),
+        ("warning", "light red", "default"),
+        ("progress_normal", "default", "brown"),
+        ("progress_complete", "default", "dark green"),
+        ("show_disconnected", "dark gray", "default"),
+        ("show_normal", "default", "default"),
+        ("show_normal_focus", "default, bold", "default"),
+        ("show_chat", "dark green", "default"),
+        ("show_chat_focus", "dark green, bold", "default"),
+        ("show_away", "brown", "default"),
+        ("show_away_focus", "brown, bold", "default"),
+        ("show_dnd", "dark red", "default"),
+        ("show_dnd_focus", "dark red, bold", "default"),
+        ("show_xa", "dark red", "default"),
+        ("show_xa_focus", "dark red, bold", "default"),
+        ("resource", "light blue", "default"),
+        ("resource_main", "dark blue", "default"),
+        ("status", "yellow", "default"),
+        ("status_focus", "yellow, bold", "default"),
+        ("param_selected", "default, bold", "dark red"),
+        ("table_selected", "default, bold", "default"),
+    ]
+    PRESENCE = {
+        "unavailable": (u"⨯", "show_disconnected"),
+        "": (u"✔", "show_normal"),
+        "chat": (u"✆", "show_chat"),
+        "away": (u"✈", "show_away"),
+        "dnd": (u"✖", "show_dnd"),
+        "xa": (u"☄", "show_xa"),
+    }
     LOG_OPT_SECTION = APP_NAME.lower()
-    LOG_OPT_OUTPUT = ('output', constants.Const.LOG_OPT_OUTPUT_SEP + constants.Const.LOG_OPT_OUTPUT_MEMORY)
+    LOG_OPT_OUTPUT = (
+        "output",
+        constants.Const.LOG_OPT_OUTPUT_SEP + constants.Const.LOG_OPT_OUTPUT_MEMORY,
+    )
 
     CONFIG_SECTION = APP_NAME.lower()
     CONFIG_OPT_KEY_PREFIX = "KEY_"
@@ -99,8 +102,8 @@
     MENU_ID_MAIN = "MAIN_MENU"
     MENU_ID_WIDGET = "WIDGET_MENU"
 
-    MODE_NORMAL = 'NORMAL'
-    MODE_INSERTION = 'INSERTION'
-    MODE_COMMAND = 'COMMAND'
+    MODE_NORMAL = "NORMAL"
+    MODE_INSERTION = "INSERTION"
+    MODE_COMMAND = "COMMAND"
 
-    GROUP_DATA_FOLDED = 'folded'
+    GROUP_DATA_FOLDED = "folded"
--- a/sat_frontends/primitivus/contact_list.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/primitivus/contact_list.py	Wed Jun 27 20:14:46 2018 +0200
@@ -27,30 +27,33 @@
 from sat_frontends.primitivus.widget import PrimitivusWidget
 from sat_frontends.tools import jid
 from sat.core import log as logging
+
 log = logging.getLogger(__name__)
 from sat_frontends.quick_frontend import quick_widgets
 
 
 class ContactList(PrimitivusWidget, QuickContactList):
-    PROFILES_MULTIPLE=False
-    PROFILES_ALLOW_NONE=False
-    signals = ['click','change']
+    PROFILES_MULTIPLE = False
+    PROFILES_ALLOW_NONE = False
+    signals = ["click", "change"]
     # FIXME: Only single profile is managed so far
 
-    def __init__(self, host, target, on_click=None, on_change=None, user_data=None, profiles=None):
+    def __init__(
+        self, host, target, on_click=None, on_change=None, user_data=None, profiles=None
+    ):
         QuickContactList.__init__(self, host, profiles)
         self.contact_list = self.host.contact_lists[self.profile]
 
-        #we now build the widget
+        # we now build the widget
         self.status_bar = StatusBar(host)
         self.frame = sat_widgets.FocusFrame(self._buildList(), None, self.status_bar)
-        PrimitivusWidget.__init__(self, self.frame, _(u'Contacts'))
+        PrimitivusWidget.__init__(self, self.frame, _(u"Contacts"))
         if on_click:
-            urwid.connect_signal(self, 'click', on_click, user_data)
+            urwid.connect_signal(self, "click", on_click, user_data)
         if on_change:
-            urwid.connect_signal(self, 'change', on_change, user_data)
-        self.host.addListener('notification', self.onNotification, [self.profile])
-        self.host.addListener('notificationsClear', self.onNotification, [self.profile])
+            urwid.connect_signal(self, "change", on_change, user_data)
+        self.host.addListener("notification", self.onNotification, [self.profile])
+        self.host.addListener("notificationsClear", self.onNotification, [self.profile])
         self.postInit()
 
     def update(self, entities=None, type_=None, profile=None):
@@ -64,21 +67,31 @@
             except IndexError:
                 pass
         self._invalidate()
-        self.host.redraw() # FIXME: check if can be avoided
+        self.host.redraw()  # FIXME: check if can be avoided
 
     def keypress(self, size, key):
         # FIXME: we have a temporary behaviour here: FOCUS_SWITCH change focus globally in the parent,
         #        and FOCUS_UP/DOWN is transwmitter to parent if we are respectively on the first or last element
         if key in sat_widgets.FOCUS_KEYS:
-            if (key == a_key['FOCUS_SWITCH'] or  (key == a_key['FOCUS_UP'] and self.frame.focus_position == 'body') or
-               (key == a_key['FOCUS_DOWN'] and self.frame.focus_position == 'footer')):
+            if (
+                key == a_key["FOCUS_SWITCH"]
+                or (key == a_key["FOCUS_UP"] and self.frame.focus_position == "body")
+                or (key == a_key["FOCUS_DOWN"] and self.frame.focus_position == "footer")
+            ):
                 return key
-        if key == a_key['STATUS_HIDE']: #user wants to (un)hide contacts' statuses
+        if key == a_key["STATUS_HIDE"]:  # user wants to (un)hide contacts' statuses
             self.contact_list.show_status = not self.contact_list.show_status
             self.update()
-        elif key == a_key['DISCONNECTED_HIDE']: #user wants to (un)hide disconnected contacts
-            self.host.bridge.setParam(C.SHOW_OFFLINE_CONTACTS, C.boolConst(not self.contact_list.show_disconnected), "General", profile_key=self.profile)
-        elif key == a_key['RESOURCES_HIDE']: #user wants to (un)hide contacts resources
+        elif (
+            key == a_key["DISCONNECTED_HIDE"]
+        ):  # user wants to (un)hide disconnected contacts
+            self.host.bridge.setParam(
+                C.SHOW_OFFLINE_CONTACTS,
+                C.boolConst(not self.contact_list.show_disconnected),
+                "General",
+                profile_key=self.profile,
+            )
+        elif key == a_key["RESOURCES_HIDE"]:  # user wants to (un)hide contacts resources
             self.contact_list.showResources(not self.contact_list.show_resources)
             self.update()
         return super(ContactList, self).keypress(size, key)
@@ -128,7 +141,7 @@
     def _groupClicked(self, group_wid):
         group = group_wid.getValue()
         data = self.contact_list.getGroupData(group)
-        data[C.GROUP_DATA_FOLDED] =  not data.setdefault(C.GROUP_DATA_FOLDED, False)
+        data[C.GROUP_DATA_FOLDED] = not data.setdefault(C.GROUP_DATA_FOLDED, False)
         self.setFocus(group)
         self.update()
 
@@ -141,7 +154,7 @@
         """
         entity = contact_wid.data
         self.host.modeHint(C.MODE_INSERTION)
-        self._emit('click', entity)
+        self._emit("click", entity)
 
     def onNotification(self, entity, notif, profile):
         notifs = list(self.host.getNotifs(C.ENTITY_ALL, profile=self.profile))
@@ -153,7 +166,17 @@
 
     # Methods to build the widget
 
-    def _buildEntityWidget(self, entity, keys=None, use_bare_jid=False, with_notifs=True, with_show_attr=True, markup_prepend=None, markup_append=None, special=False):
+    def _buildEntityWidget(
+        self,
+        entity,
+        keys=None,
+        use_bare_jid=False,
+        with_notifs=True,
+        with_show_attr=True,
+        markup_prepend=None,
+        markup_append=None,
+        special=False,
+    ):
         """Build one contact markup data
 
         @param entity (jid.JID): entity to build
@@ -180,7 +203,7 @@
         else:
             cache = self.contact_list.getCache(entity)
             for key in keys:
-                if key.startswith('cache_'):
+                if key.startswith("cache_"):
                     entity_txt = cache.get(key[6:])
                 else:
                     entity_txt = getattr(entity, key)
@@ -193,18 +216,22 @@
             show = self.contact_list.getCache(entity, C.PRESENCE_SHOW)
             if show is None:
                 show = C.PRESENCE_UNAVAILABLE
-            show_icon, entity_attr = C.PRESENCE.get(show, ('', 'default'))
+            show_icon, entity_attr = C.PRESENCE.get(show, ("", "default"))
             markup.insert(0, u"{} ".format(show_icon))
         else:
-            entity_attr = 'default'
+            entity_attr = "default"
 
-        notifs = list(self.host.getNotifs(entity, exact_jid=special, profile=self.profile))
+        notifs = list(
+            self.host.getNotifs(entity, exact_jid=special, profile=self.profile)
+        )
         if notifs:
-            header = [('cl_notifs', u'({})'.format(len(notifs))), u' ']
-            if list(self.host.getNotifs(entity.bare, C.NOTIFY_MENTION, profile=self.profile)):
-                header = ('cl_mention', header)
+            header = [("cl_notifs", u"({})".format(len(notifs))), u" "]
+            if list(
+                self.host.getNotifs(entity.bare, C.NOTIFY_MENTION, profile=self.profile)
+            ):
+                header = ("cl_mention", header)
         else:
-            header = u''
+            header = u""
 
         markup.append((entity_attr, entity_txt))
         if markup_prepend:
@@ -212,12 +239,14 @@
         if markup_append:
             markup.extend(markup_append)
 
-        widget = sat_widgets.SelectableText(markup,
-                                            selected = entity in selected,
-                                            header = header)
+        widget = sat_widgets.SelectableText(
+            markup, selected=entity in selected, header=header
+        )
         widget.data = entity
-        widget.comp = entity_txt.lower() # value to use for sorting
-        urwid.connect_signal(widget, 'change', self._contactClicked, user_args=[use_bare_jid])
+        widget.comp = entity_txt.lower()  # value to use for sorting
+        urwid.connect_signal(
+            widget, "change", self._contactClicked, user_args=[use_bare_jid]
+        )
         return widget
 
     def _buildEntities(self, content, entities):
@@ -231,25 +260,40 @@
         widgets = []  # list of built widgets
 
         for entity in entities:
-            if entity in self.contact_list._specials or not self.contact_list.entityVisible(entity):
+            if (
+                entity in self.contact_list._specials
+                or not self.contact_list.entityVisible(entity)
+            ):
                 continue
             markup_extra = []
             if self.contact_list.show_resources:
                 for resource in self.contact_list.getCache(entity, C.CONTACT_RESOURCES):
-                    resource_disp = ('resource_main' if resource == self.contact_list.getCache(entity, C.CONTACT_MAIN_RESOURCE) else 'resource', "\n  " + resource)
+                    resource_disp = (
+                        "resource_main"
+                        if resource
+                        == self.contact_list.getCache(entity, C.CONTACT_MAIN_RESOURCE)
+                        else "resource",
+                        "\n  " + resource,
+                    )
                     markup_extra.append(resource_disp)
                     if self.contact_list.show_status:
-                        status = self.contact_list.getCache(jid.JID('%s/%s' % (entity, resource)), 'status')
-                        status_disp = ('status', "\n    " + status) if status else ""
+                        status = self.contact_list.getCache(
+                            jid.JID("%s/%s" % (entity, resource)), "status"
+                        )
+                        status_disp = ("status", "\n    " + status) if status else ""
                         markup_extra.append(status_disp)
 
-
             else:
                 if self.contact_list.show_status:
-                    status = self.contact_list.getCache(entity, 'status')
-                    status_disp = ('status', "\n  " + status) if status else ""
+                    status = self.contact_list.getCache(entity, "status")
+                    status_disp = ("status", "\n  " + status) if status else ""
                     markup_extra.append(status_disp)
-            widget = self._buildEntityWidget(entity, ('cache_nick', 'cache_name', 'node'), use_bare_jid=True, markup_append=markup_extra)
+            widget = self._buildEntityWidget(
+                entity,
+                ("cache_nick", "cache_name", "node"),
+                use_bare_jid=True,
+                markup_append=markup_extra,
+            )
             widgets.append(widget)
 
         widgets.sort(key=lambda widget: widget.comp)
@@ -264,13 +308,20 @@
         for entity in specials:
             if current is not None and current.bare == entity.bare:
                 # nested entity (e.g. MUC private conversations)
-                widget = self._buildEntityWidget(entity, ('resource',), markup_prepend='  ', special=True)
+                widget = self._buildEntityWidget(
+                    entity, ("resource",), markup_prepend="  ", special=True
+                )
             else:
                 # the special widgets
                 if entity.resource:
-                    widget = self._buildEntityWidget(entity, ('resource',), special=True)
+                    widget = self._buildEntityWidget(entity, ("resource",), special=True)
                 else:
-                    widget = self._buildEntityWidget(entity, ('cache_nick', 'cache_name', 'node'), with_show_attr=False, special=True)
+                    widget = self._buildEntityWidget(
+                        entity,
+                        ("cache_nick", "cache_name", "node"),
+                        with_show_attr=False,
+                        special=True,
+                    )
             content.append(widget)
 
     def _buildList(self):
@@ -279,26 +330,35 @@
 
         self._buildSpecials(content)
         if self.contact_list._specials:
-            content.append(urwid.Divider('='))
+            content.append(urwid.Divider("="))
 
         groups = list(self.contact_list._groups)
         groups.sort(key=lambda x: x.lower() if x else x)
         for group in groups:
             data = self.contact_list.getGroupData(group)
             folded = data.get(C.GROUP_DATA_FOLDED, False)
-            jids = list(data['jids'])
-            if group is not None and (self.contact_list.anyEntityVisible(jids) or self.contact_list.show_empty_groups):
-                header = '[-]' if not folded else '[+]'
-                widget = sat_widgets.ClickableText(group, header=header + ' ')
+            jids = list(data["jids"])
+            if group is not None and (
+                self.contact_list.anyEntityVisible(jids)
+                or self.contact_list.show_empty_groups
+            ):
+                header = "[-]" if not folded else "[+]"
+                widget = sat_widgets.ClickableText(group, header=header + " ")
                 content.append(widget)
-                urwid.connect_signal(widget, 'click', self._groupClicked)
+                urwid.connect_signal(widget, "click", self._groupClicked)
             if not folded:
                 self._buildEntities(content, jids)
-        not_in_roster = set(self.contact_list._cache).difference(self.contact_list._roster).difference(self.contact_list._specials).difference((self.contact_list.whoami.bare,))
+        not_in_roster = (
+            set(self.contact_list._cache)
+            .difference(self.contact_list._roster)
+            .difference(self.contact_list._specials)
+            .difference((self.contact_list.whoami.bare,))
+        )
         if not_in_roster:
-            content.append(urwid.Divider('-'))
+            content.append(urwid.Divider("-"))
             self._buildEntities(content, not_in_roster)
 
         return urwid.ListBox(content)
 
+
 quick_widgets.register(QuickContactList, ContactList)
--- a/sat_frontends/primitivus/game_tarot.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/primitivus/game_tarot.py	Wed Jun 27 20:14:46 2018 +0200
@@ -28,7 +28,8 @@
 
 class CardDisplayer(urwid.Text):
     """Show a card"""
-    signals = ['click']
+
+    signals = ["click"]
 
     def __init__(self, card):
         self.__selected = False
@@ -39,15 +40,15 @@
         return True
 
     def keypress(self, size, key):
-        if key == a_key['CARD_SELECT']:
+        if key == a_key["CARD_SELECT"]:
             self.select(not self.__selected)
-            self._emit('click')
+            self._emit("click")
         return key
 
     def mouse_event(self, size, event, button, x, y, focus):
         if urwid.is_mouse_event(event) and button == 1:
             self.select(not self.__selected)
-            self._emit('click')
+            self._emit("click")
             return True
 
         return False
@@ -56,7 +57,7 @@
         self.__selected = state
         attr, txt = self.card.getAttrText()
         if self.__selected:
-            attr += '_selected'
+            attr += "_selected"
         self.set_text((attr, txt))
         self._invalidate()
 
@@ -75,14 +76,15 @@
 
 class Hand(urwid.WidgetWrap):
     """Used to display several cards, and manage a hand"""
-    signals = ['click']
+
+    signals = ["click"]
 
     def __init__(self, hand=[], selectable=False, on_click=None, user_data=None):
         """@param hand: list of Card"""
         self.__selectable = selectable
         self.columns = urwid.Columns([], dividechars=1)
         if on_click:
-            urwid.connect_signal(self, 'click', on_click, user_data)
+            urwid.connect_signal(self, "click", on_click, user_data)
         if hand:
             self.update(hand)
         urwid.WidgetWrap.__init__(self, self.columns)
@@ -95,9 +97,9 @@
         if CardDisplayer in [wid.__class__ for wid in self.columns.widget_list]:
             return self.columns.keypress(size, key)
         else:
-            #No card displayed, we still have to manage the clicks
-            if key == a_key['CARD_SELECT']:
-                self._emit('click', None)
+            # No card displayed, we still have to manage the clicks
+            if key == a_key["CARD_SELECT"]:
+                self._emit("click", None)
             return key
 
     def getSelected(self):
@@ -116,22 +118,23 @@
             del self.columns.column_types[:]
         except IndexError:
             pass
-        self.columns.contents.append((urwid.Text(''), ('weight', 1, False)))
+        self.columns.contents.append((urwid.Text(""), ("weight", 1, False)))
         for card in hand:
             widget = CardDisplayer(card)
             self.columns.widget_list.append(widget)
-            self.columns.column_types.append(('fixed', 3))
-            urwid.connect_signal(widget, 'click', self.__onClick)
-        self.columns.contents.append((urwid.Text(''), ('weight', 1, False)))
+            self.columns.column_types.append(("fixed", 3))
+            urwid.connect_signal(widget, "click", self.__onClick)
+        self.columns.contents.append((urwid.Text(""), ("weight", 1, False)))
         self.columns.focus_position = 1
 
     def __onClick(self, card_wid):
-        self._emit('click', card_wid)
+        self._emit("click", card_wid)
 
 
 class Card(TarotCard):
     """This class is used to represent a card, logically
     and give a text representation with attributes"""
+
     SIZE = 3  # size of a displayed card
 
     def __init__(self, suit, value):
@@ -146,25 +149,25 @@
             value = self.value[0].upper() + self.value[1]
         if self.suit == "atout":
             if self.value == "excuse":
-                suit = 'c'
+                suit = "c"
             else:
-                suit = 'A'
-            color = 'neutral'
+                suit = "A"
+            color = "neutral"
         elif self.suit == "pique":
-            suit = u'♠'
-            color = 'black'
+            suit = u"♠"
+            color = "black"
         elif self.suit == "trefle":
-            suit = u'♣'
-            color = 'black'
+            suit = u"♣"
+            color = "black"
         elif self.suit == "coeur":
-            suit = u'♥'
-            color = 'red'
+            suit = u"♥"
+            color = "red"
         elif self.suit == "carreau":
-            suit = u'♦'
-            color = 'red'
+            suit = u"♦"
+            color = "red"
         if self.bout:
-            color = 'special'
-        return ('card_%s' % color, u"%s%s" % (value, suit))
+            color = "special"
+        return ("card_%s" % color, u"%s%s" % (value, suit))
 
     def getWidget(self):
         """Return a widget representing the card"""
@@ -181,10 +184,12 @@
         """Put a card on the table
         @param location: where to put the card (top, left, bottom or right)
         @param card: Card to play or None"""
-        assert location in ['top', 'left', 'bottom', 'right']
+        assert location in ["top", "left", "bottom", "right"]
         assert isinstance(card, Card) or card == None
-        if [getattr(self, place) for place in ['top', 'left', 'bottom', 'right']].count(None) == 0:
-            #If the table is full of card, we remove them
+        if [getattr(self, place) for place in ["top", "left", "bottom", "right"]].count(
+            None
+        ) == 0:
+            # If the table is full of card, we remove them
             self.top = self.left = self.bottom = self.right = None
         setattr(self, location, card)
         self._invalidate()
@@ -199,14 +204,16 @@
         cards = {}
         max_col, = size
         separator = " - "
-        margin = max((max_col - Card.SIZE) / 2, 0) * ' '
-        margin_center = max((max_col - Card.SIZE * 2 - len(separator)) / 2, 0) * ' '
-        for location in ['top', 'left', 'bottom', 'right']:
+        margin = max((max_col - Card.SIZE) / 2, 0) * " "
+        margin_center = max((max_col - Card.SIZE * 2 - len(separator)) / 2, 0) * " "
+        for location in ["top", "left", "bottom", "right"]:
             card = getattr(self, location)
-            cards[location] = card.getAttrText() if card else Card.SIZE * ' '
-        render_wid = [urwid.Text([margin, cards['top']]),
-                      urwid.Text([margin_center, cards['left'], separator, cards['right']]),
-                      urwid.Text([margin, cards['bottom']])]
+            cards[location] = card.getAttrText() if card else Card.SIZE * " "
+        render_wid = [
+            urwid.Text([margin, cards["top"]]),
+            urwid.Text([margin_center, cards["left"], separator, cards["right"]]),
+            urwid.Text([margin, cards["bottom"]]),
+        ]
         return urwid.Pile(render_wid)
 
 
@@ -216,13 +223,20 @@
     def __init__(self, parent, referee, players):
         QuickTarotGame.__init__(self, parent, referee, players)
         self.loadCards()
-        self.top = urwid.Pile([urwid.Padding(urwid.Text(self.top_nick), 'center')])
-        #self.parent.host.debug()
+        self.top = urwid.Pile([urwid.Padding(urwid.Text(self.top_nick), "center")])
+        # self.parent.host.debug()
         self.table = Table()
-        self.center = urwid.Columns([('fixed', len(self.left_nick), urwid.Filler(urwid.Text(self.left_nick))),
-                                urwid.Filler(self.table),
-                                ('fixed', len(self.right_nick), urwid.Filler(urwid.Text(self.right_nick)))
-                               ])
+        self.center = urwid.Columns(
+            [
+                ("fixed", len(self.left_nick), urwid.Filler(urwid.Text(self.left_nick))),
+                urwid.Filler(self.table),
+                (
+                    "fixed",
+                    len(self.right_nick),
+                    urwid.Filler(urwid.Text(self.right_nick)),
+                ),
+            ]
+        )
         """urwid.Pile([urwid.Padding(self.top_card_wid,'center'),
                              urwid.Columns([('fixed',len(self.left_nick),urwid.Text(self.left_nick)),
                                             urwid.Padding(self.center_cards_wid,'center'),
@@ -231,15 +245,19 @@
                              urwid.Padding(self.bottom_card_wid,'center')
                              ])"""
         self.hand_wid = Hand(selectable=True, on_click=self.onClick)
-        self.main_frame = urwid.Frame(self.center, header=self.top, footer=self.hand_wid, focus_part='footer')
+        self.main_frame = urwid.Frame(
+            self.center, header=self.top, footer=self.hand_wid, focus_part="footer"
+        )
         urwid.WidgetWrap.__init__(self, self.main_frame)
-        self.parent.host.bridge.tarotGameReady(self.player_nick, referee, self.parent.profile)
+        self.parent.host.bridge.tarotGameReady(
+            self.player_nick, referee, self.parent.profile
+        )
 
     def loadCards(self):
         """Load all the cards in memory"""
         QuickTarotGame.loadCards(self)
-        for value in map(str, range(1, 22)) + ['excuse']:
-            card = Card('atout', value)
+        for value in map(str, range(1, 22)) + ["excuse"]:
+            card = Card("atout", value)
             self.cards[card.suit, card.value] = card
             self.deck.append(card)
         for suit in ["pique", "coeur", "carreau", "trefle"]:
@@ -252,10 +270,12 @@
         """Start a new game, with given hand"""
         if hand is []:  # reset the display after the scores have been showed
             self.resetRound()
-            for location in ['top', 'left', 'bottom', 'right']:
+            for location in ["top", "left", "bottom", "right"]:
                 self.table.putCard(location, None)
             self.parent.host.redraw()
-            self.parent.host.bridge.tarotGameReady(self.player_nick, self.referee, self.parent.profile)
+            self.parent.host.bridge.tarotGameReady(
+                self.player_nick, self.referee, self.parent.profile
+            )
             return
         QuickTarotGame.tarotGameNewHandler(self, hand)
         self.hand_wid.update(self.hand)
@@ -264,8 +284,14 @@
     def tarotGameChooseContratHandler(self, xml_data):
         """Called when the player has to select his contrat
         @param xml_data: SàT xml representation of the form"""
-        form = xmlui.create(self.parent.host, xml_data, title=_('Please choose your contrat'), flags=['NO_CANCEL'], profile=self.parent.profile)
-        form.show(valign='top')
+        form = xmlui.create(
+            self.parent.host,
+            xml_data,
+            title=_("Please choose your contrat"),
+            flags=["NO_CANCEL"],
+            profile=self.parent.profile,
+        )
+        form.show(valign="top")
 
     def tarotGameShowCardsHandler(self, game_stage, cards, data):
         """Display cards in the middle of the game (to show for e.g. chien ou poignée)"""
@@ -282,8 +308,14 @@
         if not winners and not loosers:
             title = _("Draw game")
         else:
-            title = _('You win \o/') if self.player_nick in winners else _('You loose :(')
-        form = xmlui.create(self.parent.host, xml_data, title=title, flags=['NO_CANCEL'], profile=self.parent.profile)
+            title = _("You win \o/") if self.player_nick in winners else _("You loose :(")
+        form = xmlui.create(
+            self.parent.host,
+            xml_data,
+            title=title,
+            flags=["NO_CANCEL"],
+            profile=self.parent.profile,
+        )
         form.show()
 
     def tarotGameInvalidCardsHandler(self, phase, played_cards, invalid_cards):
@@ -291,10 +323,12 @@
         @param phase: phase of the game
         @param played_cards: all the cards played
         @param invalid_cards: cards which are invalid"""
-        QuickTarotGame.tarotGameInvalidCardsHandler(self, phase, played_cards, invalid_cards)
+        QuickTarotGame.tarotGameInvalidCardsHandler(
+            self, phase, played_cards, invalid_cards
+        )
         self.hand_wid.update(self.hand)
         if self._autoplay == None:  # No dialog if there is autoplay
-            self.parent.host.barNotify(_('Cards played are invalid !'))
+            self.parent.host.barNotify(_("Cards played are invalid !"))
         self.parent.host.redraw()
 
     def tarotGameCardsPlayedHandler(self, player, cards):
@@ -305,8 +339,12 @@
         self.parent.host.redraw()
 
     def _checkState(self):
-        if isinstance(self.center.widget_list[1].original_widget, Hand):  # if we have a hand displayed
-            self.center.widget_list[1] = urwid.Filler(self.table)  # we show again the table
+        if isinstance(
+            self.center.widget_list[1].original_widget, Hand
+        ):  # if we have a hand displayed
+            self.center.widget_list[1] = urwid.Filler(
+                self.table
+            )  # we show again the table
             if self.state == "chien":
                 self.to_show = []
                 self.state = "wait"
@@ -320,18 +358,27 @@
     ##EVENTS##
     def onClick(self, hand, card_wid):
         """Called when user do an action on the hand"""
-        if not self.state in ['play', 'ecart', 'wait_for_ecart']:
-            #it's not our turn, we ignore the click
+        if not self.state in ["play", "ecart", "wait_for_ecart"]:
+            # it's not our turn, we ignore the click
             card_wid.select(False)
             return
         self._checkState()
         if self.state == "ecart":
             if len(self.hand_wid.getSelected()) == 6:
-                pop_up_widget = sat_widgets.ConfirmDialog(_("Do you put these cards in chien ?"), yes_cb=self.onEcartDone, no_cb=self.parent.host.removePopUp)
+                pop_up_widget = sat_widgets.ConfirmDialog(
+                    _("Do you put these cards in chien ?"),
+                    yes_cb=self.onEcartDone,
+                    no_cb=self.parent.host.removePopUp,
+                )
                 self.parent.host.showPopUp(pop_up_widget)
         elif self.state == "play":
             card = card_wid.getCard()
-            self.parent.host.bridge.tarotGamePlayCards(self.player_nick, self.referee, [(card.suit, card.value)], self.parent.profile)
+            self.parent.host.bridge.tarotGamePlayCards(
+                self.player_nick,
+                self.referee,
+                [(card.suit, card.value)],
+                self.parent.profile,
+            )
             self.hand.remove(card)
             self.hand_wid.update(self.hand)
             self.state = "wait"
@@ -343,6 +390,8 @@
             ecart.append((card.suit, card.value))
             self.hand.remove(card)
         self.hand_wid.update(self.hand)
-        self.parent.host.bridge.tarotGamePlayCards(self.player_nick, self.referee, ecart, self.parent.profile)
+        self.parent.host.bridge.tarotGamePlayCards(
+            self.player_nick, self.referee, ecart, self.parent.profile
+        )
         self.state = "wait"
         self.parent.host.removePopUp()
--- a/sat_frontends/primitivus/keys.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/primitivus/keys.py	Wed Jun 27 20:14:46 2018 +0200
@@ -23,47 +23,44 @@
 
 
 action_key_map.update(
-        {
+    {
         # Edit bar
         ("edit", "MODE_INSERTION"): "i",
         ("edit", "MODE_COMMAND"): ":",
         ("edit", "HISTORY_PREV"): "up",
         ("edit", "HISTORY_NEXT"): "down",
-
         # global
-        ("global", "MENU_HIDE"): 'meta m',
-        ("global", "NOTIFICATION_NEXT"): 'ctrl n',
-        ("global", "OVERLAY_HIDE"): 'ctrl s',
-        ("global", "DEBUG"): 'ctrl d',
-        ("global", "CONTACTS_HIDE"): 'f2',
-        ('global', "REFRESH_SCREEN"): "ctrl l", # ctrl l is used by Urwid to refresh screen
-
+        ("global", "MENU_HIDE"): "meta m",
+        ("global", "NOTIFICATION_NEXT"): "ctrl n",
+        ("global", "OVERLAY_HIDE"): "ctrl s",
+        ("global", "DEBUG"): "ctrl d",
+        ("global", "CONTACTS_HIDE"): "f2",
+        (
+            "global",
+            "REFRESH_SCREEN",
+        ): "ctrl l",  # ctrl l is used by Urwid to refresh screen
         # global menu
-        ("menu_global", "APP_QUIT"): 'ctrl x',
-        ("menu_global", "ROOM_JOIN"): 'meta j',
-
+        ("menu_global", "APP_QUIT"): "ctrl x",
+        ("menu_global", "ROOM_JOIN"): "meta j",
         # primitivus widgets
         ("primitivus_widget", "DECORATION_HIDE"): "meta l",
-
         # contact list
         ("contact_list", "STATUS_HIDE"): "meta s",
         ("contact_list", "DISCONNECTED_HIDE"): "meta d",
         ("contact_list", "RESOURCES_HIDE"): "meta r",
-
         # chat panel
         ("chat_panel", "OCCUPANTS_HIDE"): "meta p",
         ("chat_panel", "TIMESTAMP_HIDE"): "meta t",
         ("chat_panel", "SHORT_NICKNAME"): "meta n",
         ("chat_panel", "SUBJECT_SWITCH"): "meta s",
         ("chat_panel", "GOTO_BOTTOM"): "G",
-
-        #card game
-        ("card_game", "CARD_SELECT"): ' ',
-
-        #focus
+        # card game
+        ("card_game", "CARD_SELECT"): " ",
+        # focus
         ("focus", "FOCUS_EXTRA"): "ctrl f",
-        })
+    }
+)
 
 
-action_key_map.set_close_namespaces(tuple(), ('global', 'focus', 'menu_global'))
+action_key_map.set_close_namespaces(tuple(), ("global", "focus", "menu_global"))
 action_key_map.check_namespaces()
--- a/sat_frontends/primitivus/notify.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/primitivus/notify.py	Wed Jun 27 20:14:46 2018 +0200
@@ -19,27 +19,35 @@
 
 import dbus
 
+
 class Notify(object):
     """Used to send notification and detect if we have focus"""
 
     def __init__(self):
 
-        #X11 stuff
+        # X11 stuff
         self.display = None
         self.X11_id = -1
 
         try:
             from Xlib import display as X_display
+
             self.display = X_display.Display()
             self.X11_id = self.getFocus()
         except:
             pass
 
-        #Now we try to connect to Freedesktop D-Bus API
+        # Now we try to connect to Freedesktop D-Bus API
         try:
             bus = dbus.SessionBus()
-            db_object = bus.get_object('org.freedesktop.Notifications', '/org/freedesktop/Notifications', follow_name_owner_changes=True)
-            self.freedesktop_int = dbus.Interface(db_object, dbus_interface='org.freedesktop.Notifications')
+            db_object = bus.get_object(
+                "org.freedesktop.Notifications",
+                "/org/freedesktop/Notifications",
+                follow_name_owner_changes=True,
+            )
+            self.freedesktop_int = dbus.Interface(
+                db_object, dbus_interface="org.freedesktop.Notifications"
+            )
         except:
             self.freedesktop_int = None
 
@@ -56,7 +64,7 @@
 
     def sendNotification(self, summ_mess, body_mess=""):
         """Send notification to the user if possible"""
-        #TODO: check options before sending notifications
+        # TODO: check options before sending notifications
         if self.freedesktop_int:
             self.sendFDNotification(summ_mess, body_mess)
 
@@ -68,10 +76,17 @@
             app_icon = ""
             summary = summ_mess
             body = body_mess
-            actions = dbus.Array(signature='s')
-            hints = dbus.Dictionary(signature='sv')
+            actions = dbus.Array(signature="s")
+            hints = dbus.Dictionary(signature="sv")
             expire_timeout = -1
 
-            self.freedesktop_int.Notify(app_name, replaces_id, app_icon,
-                                        summary, body, actions,
-                                        hints, expire_timeout)
+            self.freedesktop_int.Notify(
+                app_name,
+                replaces_id,
+                app_icon,
+                summary,
+                body,
+                actions,
+                hints,
+                expire_timeout,
+            )
--- a/sat_frontends/primitivus/profile_manager.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/primitivus/profile_manager.py	Wed Jun 27 20:14:46 2018 +0200
@@ -19,6 +19,7 @@
 
 from sat.core.i18n import _
 from sat.core import log as logging
+
 log = logging.getLogger(__name__)
 from sat_frontends.quick_frontend.quick_profile_manager import QuickProfileManager
 from sat_frontends.primitivus.constants import Const as C
@@ -28,46 +29,68 @@
 
 
 class ProfileManager(QuickProfileManager, urwid.WidgetWrap):
-
     def __init__(self, host, autoconnect=None):
         QuickProfileManager.__init__(self, host, autoconnect)
 
-        #login & password box must be created before list because of onProfileChange
-        self.login_wid = sat_widgets.AdvancedEdit(_('Login:'), align='center')
-        self.pass_wid = sat_widgets.Password(_('Password:'), align='center')
+        # login & password box must be created before list because of onProfileChange
+        self.login_wid = sat_widgets.AdvancedEdit(_("Login:"), align="center")
+        self.pass_wid = sat_widgets.Password(_("Password:"), align="center")
 
-        style = ['no_first_select']
+        style = ["no_first_select"]
         profiles = host.bridge.profilesListGet()
         profiles.sort()
-        self.list_profile = sat_widgets.List(profiles, style=style, align='center', on_change=self.onProfileChange)
+        self.list_profile = sat_widgets.List(
+            profiles, style=style, align="center", on_change=self.onProfileChange
+        )
 
-        #new & delete buttons
-        buttons = [urwid.Button(_("New"), self.onNewProfile),
-                  urwid.Button(_("Delete"), self.onDeleteProfile)]
-        buttons_flow = urwid.GridFlow(buttons, max([len(button.get_label()) for button in buttons])+4, 1, 1, 'center')
+        # new & delete buttons
+        buttons = [
+            urwid.Button(_("New"), self.onNewProfile),
+            urwid.Button(_("Delete"), self.onDeleteProfile),
+        ]
+        buttons_flow = urwid.GridFlow(
+            buttons,
+            max([len(button.get_label()) for button in buttons]) + 4,
+            1,
+            1,
+            "center",
+        )
+
+        # second part: login information:
+        divider = urwid.Divider("-")
 
-        #second part: login information:
-        divider = urwid.Divider('-')
+        # connect button
+        connect_button = sat_widgets.CustomButton(
+            _("Connect"), self.onConnectProfiles, align="center"
+        )
 
-        #connect button
-        connect_button = sat_widgets.CustomButton(_("Connect"), self.onConnectProfiles, align='center')
-
-        #we now build the widget
-        list_walker = urwid.SimpleFocusListWalker([buttons_flow,self.list_profile, divider, self.login_wid, self.pass_wid, connect_button])
+        # we now build the widget
+        list_walker = urwid.SimpleFocusListWalker(
+            [
+                buttons_flow,
+                self.list_profile,
+                divider,
+                self.login_wid,
+                self.pass_wid,
+                connect_button,
+            ]
+        )
         frame_body = urwid.ListBox(list_walker)
-        frame = urwid.Frame(frame_body,urwid.AttrMap(urwid.Text(_("Profile Manager"),align='center'),'title'))
+        frame = urwid.Frame(
+            frame_body,
+            urwid.AttrMap(urwid.Text(_("Profile Manager"), align="center"), "title"),
+        )
         self.main_widget = urwid.LineBox(frame)
         urwid.WidgetWrap.__init__(self, self.main_widget)
 
         self.go(autoconnect)
 
-
     def keypress(self, size, key):
-        if key == a_key['APP_QUIT']:
+        if key == a_key["APP_QUIT"]:
             self.host.onExit()
             raise urwid.ExitMainLoop()
-        elif key in (a_key['FOCUS_UP'], a_key['FOCUS_DOWN']):
-            focus_diff = 1 if key==a_key['FOCUS_DOWN'] else -1
+        elif key in (a_key["FOCUS_UP"], a_key["FOCUS_DOWN"]):
+            focus_diff = 1 if key == a_key["FOCUS_DOWN"] else -1
             list_box = self.main_widget.base_widget.body
             current_focus = list_box.body.get_focus()[1]
             if current_focus is None:
@@ -77,7 +100,9 @@
                 if current_focus < 0 or current_focus >= len(list_box.body):
                     break
                 if list_box.body[current_focus].selectable():
-                    list_box.set_focus(current_focus, 'above' if focus_diff == 1 else 'below')
+                    list_box.set_focus(
+                        current_focus, "above" if focus_diff == 1 else "below"
+                    )
                     list_box._invalidate()
                     return
         return super(ProfileManager, self).keypress(size, key)
@@ -88,17 +113,26 @@
     def newProfile(self, button, edit):
         """Create the profile"""
         name = edit.get_edit_text()
-        self.host.bridge.profileCreate(name, callback=lambda: self.newProfileCreated(name), errback=self.profileCreationFailure)
+        self.host.bridge.profileCreate(
+            name,
+            callback=lambda: self.newProfileCreated(name),
+            errback=self.profileCreationFailure,
+        )
 
     def newProfileCreated(self, profile):
         # new profile will be selected, and a selected profile assume the session is started
-        self.host.bridge.profileStartSession('', profile, callback=lambda dummy: self.newProfileSessionStarted(profile), errback=self.profileCreationFailure)
+        self.host.bridge.profileStartSession(
+            "",
+            profile,
+            callback=lambda dummy: self.newProfileSessionStarted(profile),
+            errback=self.profileCreationFailure,
+        )
 
     def newProfileSessionStarted(self, profile):
         self.host.removePopUp()
         self.refillProfiles()
         self.list_profile.selectValue(profile)
-        self.current.profile=profile
+        self.current.profile = profile
         self.getConnectionParams(profile)
         self.host.redraw()
 
@@ -112,12 +146,23 @@
         self.host.removePopUp()
 
     def onNewProfile(self, e):
-        pop_up_widget = sat_widgets.InputDialog(_("New profile"), _("Please enter a new profile name"), cancel_cb=self.cancelDialog, ok_cb=self.newProfile)
+        pop_up_widget = sat_widgets.InputDialog(
+            _("New profile"),
+            _("Please enter a new profile name"),
+            cancel_cb=self.cancelDialog,
+            ok_cb=self.newProfile,
+        )
         self.host.showPopUp(pop_up_widget)
 
     def onDeleteProfile(self, e):
         if self.current.profile:
-            pop_up_widget = sat_widgets.ConfirmDialog(_("Are you sure you want to delete the profile {} ?").format(self.current.profile), no_cb=self.cancelDialog, yes_cb=self.deleteProfile)
+            pop_up_widget = sat_widgets.ConfirmDialog(
+                _("Are you sure you want to delete the profile {} ?").format(
+                    self.current.profile
+                ),
+                no_cb=self.cancelDialog,
+                yes_cb=self.deleteProfile,
+            )
             self.host.showPopUp(pop_up_widget)
 
     def onConnectProfiles(self, button):
@@ -149,7 +194,7 @@
     def setJID(self, jid_):
         self.login_wid.set_edit_text(jid_)
         self.current.login = jid_
-        self.host.redraw() # FIXME: redraw should be avoided
+        self.host.redraw()  # FIXME: redraw should be avoided
 
     def setPassword(self, password):
         self.pass_wid.set_edit_text(password)
@@ -164,16 +209,20 @@
         self.updateConnectionParams()
         focused = list_wid.focus
         selected = focused.getState() if focused is not None else False
-        if not selected: # profile was just unselected
+        if not selected:  # profile was just unselected
             return
-        focused.setState(False, invisible=True) # we don't want the widget to be selected until we are sure we can access it
+        focused.setState(
+            False, invisible=True
+        )  # we don't want the widget to be selected until we are sure we can access it
+
         def authenticate_cb(data, cb_id, profile):
-            if C.bool(data.pop('validated', C.BOOL_FALSE)):
+            if C.bool(data.pop("validated", C.BOOL_FALSE)):
                 self.current.profile = profile
                 focused.setState(True, invisible=True)
                 self.getConnectionParams(profile)
                 self.host.redraw()
             self.host.actionManager(data, callback=authenticate_cb, profile=profile)
 
-        self.host.launchAction(C.AUTHENTICATE_PROFILE_ID, callback=authenticate_cb, profile=focused.text)
-
+        self.host.launchAction(
+            C.AUTHENTICATE_PROFILE_ID, callback=authenticate_cb, profile=focused.text
+        )
--- a/sat_frontends/primitivus/progress.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/primitivus/progress.py	Wed Jun 27 20:14:46 2018 +0200
@@ -34,54 +34,58 @@
         self.progress_dict = {}
         listbox = urwid.ListBox(self.progress_list)
         buttons = []
-        buttons.append(sat_widgets.CustomButton(_('Clear progress list'), self._onClear))
+        buttons.append(sat_widgets.CustomButton(_("Clear progress list"), self._onClear))
         max_len = max([button.getSize() for button in buttons])
-        buttons_wid = urwid.GridFlow(buttons,max_len,1,0,'center')
+        buttons_wid = urwid.GridFlow(buttons, max_len, 1, 0, "center")
         main_wid = sat_widgets.FocusFrame(listbox, footer=buttons_wid)
         urwid.WidgetWrap.__init__(self, main_wid)
 
     def add(self, progress_id, message, profile):
         mess_wid = urwid.Text(message)
-        progr_wid = urwid.ProgressBar('progress_normal', 'progress_complete')
+        progr_wid = urwid.ProgressBar("progress_normal", "progress_complete")
         column = urwid.Columns([mess_wid, progr_wid])
-        self.progress_dict[(progress_id, profile)] = {'full':column,'progress':progr_wid,'state':'init'}
+        self.progress_dict[(progress_id, profile)] = {
+            "full": column,
+            "progress": progr_wid,
+            "state": "init",
+        }
         self.progress_list.append(column)
         self.progressCB(self.host.loop, (progress_id, message, profile))
 
     def progressCB(self, loop, data):
         progress_id, message, profile = data
         data = self.host.bridge.progressGet(progress_id, profile)
-        pbar = self.progress_dict[(progress_id, profile)]['progress']
+        pbar = self.progress_dict[(progress_id, profile)]["progress"]
         if data:
-            if self.progress_dict[(progress_id, profile)]['state'] == 'init':
-                #first answer, we must construct the bar
-                self.progress_dict[(progress_id, profile)]['state'] = 'progress'
-                pbar.done = float(data['size'])
+            if self.progress_dict[(progress_id, profile)]["state"] == "init":
+                # first answer, we must construct the bar
+                self.progress_dict[(progress_id, profile)]["state"] = "progress"
+                pbar.done = float(data["size"])
 
-            pbar.set_completion(float(data['position']))
+            pbar.set_completion(float(data["position"]))
             self.updateNotBar()
         else:
-            if self.progress_dict[(progress_id, profile)]['state'] == 'progress':
-                self.progress_dict[(progress_id, profile)]['state'] = 'done'
+            if self.progress_dict[(progress_id, profile)]["state"] == "progress":
+                self.progress_dict[(progress_id, profile)]["state"] = "done"
                 pbar.set_completion(pbar.done)
                 self.updateNotBar()
                 return
 
-        loop.set_alarm_in(0.2,self.progressCB, (progress_id, message, profile))
+        loop.set_alarm_in(0.2, self.progressCB, (progress_id, message, profile))
 
     def _removeBar(self, progress_id, profile):
-        wid = self.progress_dict[(progress_id, profile)]['full']
+        wid = self.progress_dict[(progress_id, profile)]["full"]
         self.progress_list.remove(wid)
-        del(self.progress_dict[(progress_id, profile)])
+        del (self.progress_dict[(progress_id, profile)])
 
     def _onClear(self, button):
-       to_remove = []
-       for progress_id, profile in self.progress_dict:
-           if self.progress_dict[(progress_id, profile)]['state'] == 'done':
-               to_remove.append((progress_id, profile))
-       for progress_id, profile in to_remove:
-           self._removeBar(progress_id, profile)
-       self.updateNotBar()
+        to_remove = []
+        for progress_id, profile in self.progress_dict:
+            if self.progress_dict[(progress_id, profile)]["state"] == "done":
+                to_remove.append((progress_id, profile))
+        for progress_id, profile in to_remove:
+            self._removeBar(progress_id, profile)
+        self.updateNotBar()
 
     def updateNotBar(self):
         if not self.progress_dict:
@@ -90,9 +94,8 @@
         progress = 0
         nb_bars = 0
         for progress_id, profile in self.progress_dict:
-            pbar = self.progress_dict[(progress_id, profile)]['progress']
-            progress += pbar.current/pbar.done*100
-            nb_bars+=1
-        av_progress = progress/float(nb_bars)
+            pbar = self.progress_dict[(progress_id, profile)]["progress"]
+            progress += pbar.current / pbar.done * 100
+            nb_bars += 1
+        av_progress = progress / float(nb_bars)
         self.host.setProgress(av_progress)
-
--- a/sat_frontends/primitivus/status.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/primitivus/status.py	Wed Jun 27 20:14:46 2018 +0200
@@ -25,51 +25,90 @@
 
 
 class StatusBar(urwid.Columns):
-
     def __init__(self, host):
         self.host = host
-        self.presence = sat_widgets.ClickableText('')
-        status_prefix = urwid.Text('[')
-        status_suffix = urwid.Text(']')
-        self.status = sat_widgets.ClickableText('')
-        self.setPresenceStatus(C.PRESENCE_UNAVAILABLE, '')
-        urwid.Columns.__init__(self, [('weight', 1, self.presence), ('weight', 1, status_prefix),
-                                      ('weight', 9, self.status), ('weight', 1, status_suffix)])
-        urwid.connect_signal(self.presence, 'click', self.onPresenceClick)
-        urwid.connect_signal(self.status, 'click', self.onStatusClick)
+        self.presence = sat_widgets.ClickableText("")
+        status_prefix = urwid.Text("[")
+        status_suffix = urwid.Text("]")
+        self.status = sat_widgets.ClickableText("")
+        self.setPresenceStatus(C.PRESENCE_UNAVAILABLE, "")
+        urwid.Columns.__init__(
+            self,
+            [
+                ("weight", 1, self.presence),
+                ("weight", 1, status_prefix),
+                ("weight", 9, self.status),
+                ("weight", 1, status_suffix),
+            ],
+        )
+        urwid.connect_signal(self.presence, "click", self.onPresenceClick)
+        urwid.connect_signal(self.status, "click", self.onStatusClick)
 
     def onPresenceClick(self, sender=None):
-        if not self.host.bridge.isConnected(self.host.current_profile):  # FIXME: manage multi-profiles
+        if not self.host.bridge.isConnected(
+            self.host.current_profile
+        ):  # FIXME: manage multi-profiles
             return
         options = [commonConst.PRESENCE[presence] for presence in commonConst.PRESENCE]
-        list_widget = sat_widgets.GenericList(options=options, option_type=sat_widgets.ClickableText, on_click=self.onChange)
-        decorated = sat_widgets.LabelLine(list_widget, sat_widgets.SurroundedText(_('Set your presence')))
+        list_widget = sat_widgets.GenericList(
+            options=options, option_type=sat_widgets.ClickableText, on_click=self.onChange
+        )
+        decorated = sat_widgets.LabelLine(
+            list_widget, sat_widgets.SurroundedText(_("Set your presence"))
+        )
         self.host.showPopUp(decorated)
 
     def onStatusClick(self, sender=None):
-        if not self.host.bridge.isConnected(self.host.current_profile):  # FIXME: manage multi-profiles
+        if not self.host.bridge.isConnected(
+            self.host.current_profile
+        ):  # FIXME: manage multi-profiles
             return
-        pop_up_widget = sat_widgets.InputDialog(_('Set your status'), _('New status'), default_txt=self.status.get_text(),
-                                                cancel_cb=self.host.removePopUp, ok_cb=self.onChange)
+        pop_up_widget = sat_widgets.InputDialog(
+            _("Set your status"),
+            _("New status"),
+            default_txt=self.status.get_text(),
+            cancel_cb=self.host.removePopUp,
+            ok_cb=self.onChange,
+        )
         self.host.showPopUp(pop_up_widget)
 
     def onChange(self, sender=None, user_data=None):
         new_value = user_data.get_text()
-        previous = ([key for key in C.PRESENCE if C.PRESENCE[key][0] == self.presence.get_text()][0], self.status.get_text())
+        previous = (
+            [key for key in C.PRESENCE if C.PRESENCE[key][0] == self.presence.get_text()][
+                0
+            ],
+            self.status.get_text(),
+        )
         if isinstance(user_data, sat_widgets.ClickableText):
-            new = ([key for key in commonConst.PRESENCE if commonConst.PRESENCE[key] == new_value][0], previous[1])
+            new = (
+                [
+                    key
+                    for key in commonConst.PRESENCE
+                    if commonConst.PRESENCE[key] == new_value
+                ][0],
+                previous[1],
+            )
         elif isinstance(user_data, sat_widgets.AdvancedEdit):
             new = (previous[0], new_value[0])
         if new != previous:
-            statuses = {C.PRESENCE_STATUSES_DEFAULT: new[1]}  # FIXME: manage multilingual statuses
-            for profile in self.host.profiles:  # FIXME: for now all the profiles share the same status
-                self.host.bridge.setPresence(show=new[0], statuses=statuses, profile_key=profile)
+            statuses = {
+                C.PRESENCE_STATUSES_DEFAULT: new[1]
+            }  # FIXME: manage multilingual statuses
+            for (
+                profile
+            ) in (
+                self.host.profiles
+            ):  # FIXME: for now all the profiles share the same status
+                self.host.bridge.setPresence(
+                    show=new[0], statuses=statuses, profile_key=profile
+                )
             self.setPresenceStatus(new[0], new[1])
         self.host.removePopUp()
 
     def setPresenceStatus(self, show, status):
         show_icon, show_attr = C.PRESENCE.get(show)
-        self.presence.set_text(('show_normal', show_icon))
+        self.presence.set_text(("show_normal", show_icon))
         if status is not None:
             self.status.set_text((show_attr, status))
         self.host.redraw()
--- a/sat_frontends/primitivus/widget.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/primitivus/widget.py	Wed Jun 27 20:14:46 2018 +0200
@@ -18,6 +18,7 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 from sat.core import log as logging
+
 log = logging.getLogger(__name__)
 import urwid
 from urwid_satext import sat_widgets
@@ -27,7 +28,7 @@
 class PrimitivusWidget(urwid.WidgetWrap):
     """Base widget for Primitivus"""
 
-    def __init__(self, w, title=''):
+    def __init__(self, w, title=""):
         self._title = title
         self._title_dynamic = None
         self._original_widget = w
@@ -57,9 +58,9 @@
             title_elts.append(self._title)
         if self._title_dynamic:
             title_elts.append(self._title_dynamic)
-        if len(all_profiles)>1 and profiles:
-            title_elts.append(u'[{}]'.format(u', '.join(profiles)))
-        return sat_widgets.SurroundedText(u' '.join(title_elts))
+        if len(all_profiles) > 1 and profiles:
+            title_elts.append(u"[{}]".format(u", ".join(profiles)))
+        return sat_widgets.SurroundedText(u" ".join(title_elts))
 
     @title.setter
     def title(self, value):
@@ -83,9 +84,8 @@
         """True if the decoration is visible"""
         return isinstance(self._w, sat_widgets.LabelLine)
 
-
     def keypress(self, size, key):
-        if key == a_key['DECORATION_HIDE']: #user wants to (un)hide widget decoration
+        if key == a_key["DECORATION_HIDE"]:  # user wants to (un)hide widget decoration
             show = not self.decorationVisible
             self.showDecoration(show)
         else:
@@ -96,7 +96,9 @@
 
     def showDecoration(self, show=True):
         """Show/Hide the decoration around the window"""
-        self._w = self._getDecoration(self._original_widget) if show else self._original_widget
+        self._w = (
+            self._getDecoration(self._original_widget) if show else self._original_widget
+        )
 
     def getMenu(self):
         raise NotImplementedError
--- a/sat_frontends/primitivus/xmlui.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/primitivus/xmlui.py	Wed Jun 27 20:14:46 2018 +0200
@@ -24,6 +24,7 @@
 from urwid_satext import sat_widgets
 from urwid_satext import files_management
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from sat_frontends.primitivus.constants import Const as C
 from sat_frontends.primitivus.widget import PrimitivusWidget
@@ -39,25 +40,22 @@
 
     def _xmluiOnChange(self, callback):
         """ Call callback with widget as only argument """
-        urwid.connect_signal(self, 'change', self._event_callback, callback)
+        urwid.connect_signal(self, "change", self._event_callback, callback)
 
 
 class PrimitivusEmptyWidget(xmlui.EmptyWidget, urwid.Text):
-
     def __init__(self, _xmlui_parent):
-        urwid.Text.__init__(self, '')
+        urwid.Text.__init__(self, "")
 
 
 class PrimitivusTextWidget(xmlui.TextWidget, urwid.Text):
-
     def __init__(self, _xmlui_parent, value, read_only=False):
         urwid.Text.__init__(self, value)
 
 
 class PrimitivusLabelWidget(xmlui.LabelWidget, PrimitivusTextWidget):
-
     def __init__(self, _xmlui_parent, value):
-        super(PrimitivusLabelWidget, self).__init__(_xmlui_parent, value+": ")
+        super(PrimitivusLabelWidget, self).__init__(_xmlui_parent, value + ": ")
 
 
 class PrimitivusJidWidget(xmlui.JidWidget, PrimitivusTextWidget):
@@ -65,27 +63,27 @@
 
 
 class PrimitivusDividerWidget(xmlui.DividerWidget, urwid.Divider):
-
-    def __init__(self, _xmlui_parent, style='line'):
-        if style == 'line':
-            div_char = u'─'
-        elif style == 'dot':
-            div_char = u'·'
-        elif style == 'dash':
-            div_char = u'-'
-        elif style == 'plain':
-            div_char = u'█'
-        elif style == 'blank':
-            div_char = ' '
+    def __init__(self, _xmlui_parent, style="line"):
+        if style == "line":
+            div_char = u"─"
+        elif style == "dot":
+            div_char = u"·"
+        elif style == "dash":
+            div_char = u"-"
+        elif style == "plain":
+            div_char = u"█"
+        elif style == "blank":
+            div_char = " "
         else:
             log.warning(_("Unknown div_char"))
-            div_char = u'─'
+            div_char = u"─"
 
         urwid.Divider.__init__(self, div_char)
 
 
-class PrimitivusStringWidget(xmlui.StringWidget, sat_widgets.AdvancedEdit, PrimitivusEvents):
-
+class PrimitivusStringWidget(
+    xmlui.StringWidget, sat_widgets.AdvancedEdit, PrimitivusEvents
+):
     def __init__(self, _xmlui_parent, value, read_only=False):
         sat_widgets.AdvancedEdit.__init__(self, edit_text=value)
         self.read_only = read_only
@@ -106,8 +104,9 @@
     pass
 
 
-class PrimitivusPasswordWidget(xmlui.PasswordWidget, sat_widgets.Password, PrimitivusEvents):
-
+class PrimitivusPasswordWidget(
+    xmlui.PasswordWidget, sat_widgets.Password, PrimitivusEvents
+):
     def __init__(self, _xmlui_parent, value, read_only=False):
         sat_widgets.Password.__init__(self, edit_text=value)
         self.read_only = read_only
@@ -124,8 +123,9 @@
         return self.get_edit_text()
 
 
-class PrimitivusTextBoxWidget(xmlui.TextBoxWidget, sat_widgets.AdvancedEdit, PrimitivusEvents):
-
+class PrimitivusTextBoxWidget(
+    xmlui.TextBoxWidget, sat_widgets.AdvancedEdit, PrimitivusEvents
+):
     def __init__(self, _xmlui_parent, value, read_only=False):
         sat_widgets.AdvancedEdit.__init__(self, edit_text=value, multiline=True)
         self.read_only = read_only
@@ -143,9 +143,8 @@
 
 
 class PrimitivusBoolWidget(xmlui.BoolWidget, urwid.CheckBox, PrimitivusEvents):
-
     def __init__(self, _xmlui_parent, state, read_only=False):
-        urwid.CheckBox.__init__(self, '', state=state)
+        urwid.CheckBox.__init__(self, "", state=state)
         self.read_only = read_only
 
     def selectable(self):
@@ -161,7 +160,6 @@
 
 
 class PrimitivusIntWidget(xmlui.IntWidget, sat_widgets.AdvancedEdit, PrimitivusEvents):
-
     def __init__(self, _xmlui_parent, value, read_only=False):
         sat_widgets.AdvancedEdit.__init__(self, edit_text=value)
         self.read_only = read_only
@@ -178,17 +176,17 @@
         return self.get_edit_text()
 
 
-class PrimitivusButtonWidget(xmlui.ButtonWidget, sat_widgets.CustomButton, PrimitivusEvents):
-
+class PrimitivusButtonWidget(
+    xmlui.ButtonWidget, sat_widgets.CustomButton, PrimitivusEvents
+):
     def __init__(self, _xmlui_parent, value, click_callback):
         sat_widgets.CustomButton.__init__(self, value, on_press=click_callback)
 
     def _xmluiOnClick(self, callback):
-            urwid.connect_signal(self, 'click', callback)
+        urwid.connect_signal(self, "click", callback)
 
 
 class PrimitivusListWidget(xmlui.ListWidget, sat_widgets.List, PrimitivusEvents):
-
     def __init__(self, _xmlui_parent, options, selected, flags):
         sat_widgets.List.__init__(self, options=options, style=flags)
         self._xmluiSelectValues(selected)
@@ -217,13 +215,18 @@
                     selected.append(value)
             self._xmluiSelectValues(selected)
 
+
 class PrimitivusJidsListWidget(xmlui.ListWidget, sat_widgets.List, PrimitivusEvents):
-
     def __init__(self, _xmlui_parent, jids, styles):
-        sat_widgets.List.__init__(self, options=jids+[''], # the empty field is here to add new jids if needed
-                                  option_type=lambda txt, align: sat_widgets.AdvancedEdit(edit_text=txt, align=align),
-                                  on_change=self._onChange)
-        self.delete=0
+        sat_widgets.List.__init__(
+            self,
+            options=jids + [""],  # the empty field is here to add new jids if needed
+            option_type=lambda txt, align: sat_widgets.AdvancedEdit(
+                edit_text=txt, align=align
+            ),
+            on_change=self._onChange,
+        )
+        self.delete = 0
 
     def _onChange(self, list_widget, jid_widget=None, text=None):
         if jid_widget is not None:
@@ -239,13 +242,16 @@
         return [jid_ for jid_ in self.getAllValues() if jid_]
 
 
-class PrimitivusAdvancedListContainer(xmlui.AdvancedListContainer, sat_widgets.TableContainer, PrimitivusEvents):
-
-    def __init__(self, _xmlui_parent, columns, selectable='no'):
-        options = {'ADAPT':()}
-        if selectable != 'no':
-            options['HIGHLIGHT'] = ()
-        sat_widgets.TableContainer.__init__(self, columns=columns, options=options, row_selectable = selectable!='no')
+class PrimitivusAdvancedListContainer(
+    xmlui.AdvancedListContainer, sat_widgets.TableContainer, PrimitivusEvents
+):
+    def __init__(self, _xmlui_parent, columns, selectable="no"):
+        options = {"ADAPT": ()}
+        if selectable != "no":
+            options["HIGHLIGHT"] = ()
+        sat_widgets.TableContainer.__init__(
+            self, columns=columns, options=options, row_selectable=selectable != "no"
+        )
 
     def _xmluiAppend(self, widget):
         self.addWidget(widget)
@@ -261,21 +267,20 @@
 
     def _xmluiOnSelect(self, callback):
         """ Call callback with widget as only argument """
-        urwid.connect_signal(self, 'click', self._event_callback, callback)
+        urwid.connect_signal(self, "click", self._event_callback, callback)
 
 
 class PrimitivusPairsContainer(xmlui.PairsContainer, sat_widgets.TableContainer):
-
     def __init__(self, _xmlui_parent):
-        options = {'ADAPT':(0,), 'HIGHLIGHT':(0,)}
-        if self._xmlui_main.type == 'param':
-            options['FOCUS_ATTR'] = 'param_selected'
+        options = {"ADAPT": (0,), "HIGHLIGHT": (0,)}
+        if self._xmlui_main.type == "param":
+            options["FOCUS_ATTR"] = "param_selected"
         sat_widgets.TableContainer.__init__(self, columns=2, options=options)
 
     def _xmluiAppend(self, widget):
         if isinstance(widget, PrimitivusEmptyWidget):
             # we don't want highlight on empty widgets
-            widget = urwid.AttrMap(widget, 'default')
+            widget = urwid.AttrMap(widget, "default")
         self.addWidget(widget)
 
 
@@ -284,7 +289,6 @@
 
 
 class PrimitivusTabsContainer(xmlui.TabsContainer, sat_widgets.TabsContainer):
-
     def __init__(self, _xmlui_parent):
         sat_widgets.TabsContainer.__init__(self)
 
@@ -305,7 +309,7 @@
         self._last_size = None
 
     def _xmluiAppend(self, widget):
-        if 'flow' not in widget.sizing():
+        if "flow" not in widget.sizing():
             widget = urwid.BoxAdapter(widget, self.BOX_HEIGHT)
         self.body.append(widget)
 
@@ -324,7 +328,6 @@
 
 
 class PrimitivusDialog(object):
-
     def __init__(self, _xmlui_parent):
         self.host = _xmlui_parent.host
 
@@ -336,11 +339,12 @@
 
 
 class PrimitivusMessageDialog(PrimitivusDialog, xmlui.MessageDialog, sat_widgets.Alert):
-
     def __init__(self, _xmlui_parent, title, message, level):
         PrimitivusDialog.__init__(self, _xmlui_parent)
         xmlui.MessageDialog.__init__(self, _xmlui_parent)
-        sat_widgets.Alert.__init__(self, title, message, ok_cb=lambda dummy: self._xmluiClose())
+        sat_widgets.Alert.__init__(
+            self, title, message, ok_cb=lambda dummy: self._xmluiClose()
+        )
 
 
 class PrimitivusNoteDialog(xmlui.NoteDialog, PrimitivusMessageDialog):
@@ -348,41 +352,51 @@
     pass
 
 
-class PrimitivusConfirmDialog(PrimitivusDialog, xmlui.ConfirmDialog, sat_widgets.ConfirmDialog):
-
+class PrimitivusConfirmDialog(
+    PrimitivusDialog, xmlui.ConfirmDialog, sat_widgets.ConfirmDialog
+):
     def __init__(self, _xmlui_parent, title, message, level, buttons_set):
         PrimitivusDialog.__init__(self, _xmlui_parent)
         xmlui.ConfirmDialog.__init__(self, _xmlui_parent)
-        sat_widgets.ConfirmDialog.__init__(self, title, message, no_cb=lambda dummy: self._xmluiCancelled(), yes_cb=lambda dummy: self._xmluiValidated())
+        sat_widgets.ConfirmDialog.__init__(
+            self,
+            title,
+            message,
+            no_cb=lambda dummy: self._xmluiCancelled(),
+            yes_cb=lambda dummy: self._xmluiValidated(),
+        )
 
 
-class PrimitivusFileDialog(PrimitivusDialog, xmlui.FileDialog, files_management.FileDialog):
-
+class PrimitivusFileDialog(
+    PrimitivusDialog, xmlui.FileDialog, files_management.FileDialog
+):
     def __init__(self, _xmlui_parent, title, message, level, filetype):
         # TODO: message is not managed yet
         PrimitivusDialog.__init__(self, _xmlui_parent)
         xmlui.FileDialog.__init__(self, _xmlui_parent)
         style = []
         if filetype == C.XMLUI_DATA_FILETYPE_DIR:
-            style.append('dir')
-        files_management.FileDialog.__init__(self,
-            ok_cb=lambda path: self._xmluiValidated({'path': path}),
+            style.append("dir")
+        files_management.FileDialog.__init__(
+            self,
+            ok_cb=lambda path: self._xmluiValidated({"path": path}),
             cancel_cb=lambda dummy: self._xmluiCancelled(),
             message=message,
             title=title,
-            style=style)
+            style=style,
+        )
 
 
 class GenericFactory(object):
-
     def __getattr__(self, attr):
         if attr.startswith("create"):
-            cls = globals()["Primitivus" + attr[6:]] # XXX: we prefix with "Primitivus" to work around an Urwid bug, WidgetMeta in Urwid don't manage multiple inheritance with same names
+            cls = globals()[
+                "Primitivus" + attr[6:]
+            ]  # XXX: we prefix with "Primitivus" to work around an Urwid bug, WidgetMeta in Urwid don't manage multiple inheritance with same names
             return cls
 
 
 class WidgetFactory(GenericFactory):
-
     def __getattr__(self, attr):
         if attr.startswith("create"):
             cls = GenericFactory.__getattr__(self, attr)
@@ -393,48 +407,66 @@
 class XMLUIPanel(xmlui.XMLUIPanel, PrimitivusWidget):
     widget_factory = WidgetFactory()
 
-    def __init__(self, host, parsed_xml, title=None, flags=None, callback=None, ignore=None, whitelist=None, profile=C.PROF_KEY_NONE):
+    def __init__(
+        self,
+        host,
+        parsed_xml,
+        title=None,
+        flags=None,
+        callback=None,
+        ignore=None,
+        whitelist=None,
+        profile=C.PROF_KEY_NONE,
+    ):
         self.widget_factory._xmlui_main = self
         self._dest = None
-        xmlui.XMLUIPanel.__init__(self,
-                                  host,
-                                  parsed_xml,
-                                  title = title,
-                                  flags = flags,
-                                  callback = callback,
-                                  ignore = ignore,
-                                  profile = profile)
+        xmlui.XMLUIPanel.__init__(
+            self,
+            host,
+            parsed_xml,
+            title=title,
+            flags=flags,
+            callback=callback,
+            ignore=ignore,
+            profile=profile,
+        )
         PrimitivusWidget.__init__(self, self.main_cont, self.xmlui_title)
 
     def constructUI(self, parsed_dom):
         def postTreat():
             assert self.main_cont.body
 
-            if self.type in ('form', 'popup'):
+            if self.type in ("form", "popup"):
                 buttons = []
-                if self.type == 'form':
-                    buttons.append(urwid.Button(_('Submit'), self.onFormSubmitted))
-                    if not 'NO_CANCEL' in self.flags:
-                        buttons.append(urwid.Button(_('Cancel'), self.onFormCancelled))
+                if self.type == "form":
+                    buttons.append(urwid.Button(_("Submit"), self.onFormSubmitted))
+                    if not "NO_CANCEL" in self.flags:
+                        buttons.append(urwid.Button(_("Cancel"), self.onFormCancelled))
                 else:
-                    buttons.append(urwid.Button(_('OK'), on_press=lambda dummy: self._xmluiClose()))
+                    buttons.append(
+                        urwid.Button(_("OK"), on_press=lambda dummy: self._xmluiClose())
+                    )
                 max_len = max([len(button.get_label()) for button in buttons])
-                grid_wid = urwid.GridFlow(buttons, max_len + 4, 1, 0, 'center')
+                grid_wid = urwid.GridFlow(buttons, max_len + 4, 1, 0, "center")
                 self.main_cont.body.append(grid_wid)
-            elif self.type == 'param':
+            elif self.type == "param":
                 tabs_cont = self.main_cont.body[0].base_widget
-                assert isinstance(tabs_cont,sat_widgets.TabsContainer)
+                assert isinstance(tabs_cont, sat_widgets.TabsContainer)
                 buttons = []
-                buttons.append(sat_widgets.CustomButton(_('Save'),self.onSaveParams))
-                buttons.append(sat_widgets.CustomButton(_('Cancel'),lambda x:self.host.removeWindow()))
+                buttons.append(sat_widgets.CustomButton(_("Save"), self.onSaveParams))
+                buttons.append(
+                    sat_widgets.CustomButton(
+                        _("Cancel"), lambda x: self.host.removeWindow()
+                    )
+                )
                 max_len = max([button.getSize() for button in buttons])
-                grid_wid = urwid.GridFlow(buttons,max_len,1,0,'center')
+                grid_wid = urwid.GridFlow(buttons, max_len, 1, 0, "center")
                 tabs_cont.addFooter(grid_wid)
 
         xmlui.XMLUIPanel.constructUI(self, parsed_dom, postTreat)
         urwid.WidgetWrap.__init__(self, self.main_cont)
 
-    def show(self, show_type=None, valign='middle'):
+    def show(self, show_type=None, valign="middle"):
         """Show the constructed UI
         @param show_type: how to show the UI:
             - None (follow XMLUI's recommendation)
@@ -445,30 +477,32 @@
 
         """
         if show_type is None:
-            if self.type in ('window', 'param'):
-                show_type = 'window'
-            elif self.type in ('popup', 'form'):
-                show_type = 'popup'
+            if self.type in ("window", "param"):
+                show_type = "window"
+            elif self.type in ("popup", "form"):
+                show_type = "popup"
 
-        if show_type not in ('popup', 'window'):
-            raise ValueError('Invalid show_type [%s]' % show_type)
+        if show_type not in ("popup", "window"):
+            raise ValueError("Invalid show_type [%s]" % show_type)
 
         self._dest = show_type
-        if show_type == 'popup':
+        if show_type == "popup":
             self.host.showPopUp(self, valign=valign)
-        elif show_type == 'window':
+        elif show_type == "window":
             self.host.newWidget(self)
         else:
-           assert False
+            assert False
         self.host.redraw()
 
     def _xmluiClose(self):
-        if self._dest == 'window':
+        if self._dest == "window":
             self.host.removeWindow()
-        elif self._dest == 'popup':
+        elif self._dest == "popup":
             self.host.removePopUp(self)
         else:
-            raise exceptions.InternalError("self._dest unknown, are you sure you have called XMLUI.show ?")
+            raise exceptions.InternalError(
+                "self._dest unknown, are you sure you have called XMLUI.show ?"
+            )
 
 
 class XMLUIDialog(xmlui.XMLUIDialog):
--- a/sat_frontends/quick_frontend/constants.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/quick_frontend/constants.py	Wed Jun 27 20:14:46 2018 +0200
@@ -24,11 +24,15 @@
 
 class Const(constants.Const):
 
-    PRESENCE = OrderedDict([("", _("Online")),
-                            ("chat", _("Free for chat")),
-                            ("away", _("Away from keyboard")),
-                            ("dnd", _("Do not disturb")),
-                            ("xa", _("Extended away"))])
+    PRESENCE = OrderedDict(
+        [
+            ("", _("Online")),
+            ("chat", _("Free for chat")),
+            ("away", _("Away from keyboard")),
+            ("dnd", _("Do not disturb")),
+            ("xa", _("Extended away")),
+        ]
+    )
 
     # from plugin_misc_text_syntaxes
     SYNTAX_XHTML = "XHTML"
@@ -42,27 +46,32 @@
     XMLUI_STATUS_CANCELLED = constants.Const.XMLUI_DATA_CANCELLED
 
     # Roster
-    CONTACT_GROUPS = 'groups'
-    CONTACT_RESOURCES = 'resources'
-    CONTACT_MAIN_RESOURCE = 'main_resource'
-    CONTACT_SPECIAL = 'special'
-    CONTACT_SPECIAL_GROUP = 'group'  # group chat special entity
-    CONTACT_SELECTED = 'selected'
+    CONTACT_GROUPS = "groups"
+    CONTACT_RESOURCES = "resources"
+    CONTACT_MAIN_RESOURCE = "main_resource"
+    CONTACT_SPECIAL = "special"
+    CONTACT_SPECIAL_GROUP = "group"  # group chat special entity
+    CONTACT_SELECTED = "selected"
     # used in handler to track where the contact is coming from
-    CONTACT_PROFILE = 'profile'
+    CONTACT_PROFILE = "profile"
     CONTACT_SPECIAL_ALLOWED = (CONTACT_SPECIAL_GROUP,)  # allowed values for special flag
     # set of forbidden names for contact data
-    CONTACT_DATA_FORBIDDEN = {CONTACT_GROUPS, CONTACT_RESOURCES, CONTACT_MAIN_RESOURCE,
-                              CONTACT_SELECTED, CONTACT_PROFILE}
+    CONTACT_DATA_FORBIDDEN = {
+        CONTACT_GROUPS,
+        CONTACT_RESOURCES,
+        CONTACT_MAIN_RESOURCE,
+        CONTACT_SELECTED,
+        CONTACT_PROFILE,
+    }
 
     # Chats
     CHAT_STATE_ICON = {
         "": u" ",
-        "active": u'✔',
-        "inactive": u'☄',
-        "gone": u'✈',
-        "composing": u'✎',
-        "paused": u"…"
+        "active": u"✔",
+        "inactive": u"☄",
+        "gone": u"✈",
+        "composing": u"✎",
+        "paused": u"…",
     }
 
     # Blogs
@@ -72,26 +81,36 @@
 
     # Widgets management
     # FIXME: should be in quick_frontend.constant, but Libervia doesn't inherit from it
-    WIDGET_NEW = 'NEW'
-    WIDGET_KEEP = 'KEEP'
-    WIDGET_RAISE = 'RAISE'
-    WIDGET_RECREATE = 'RECREATE'
+    WIDGET_NEW = "NEW"
+    WIDGET_KEEP = "KEEP"
+    WIDGET_RAISE = "RAISE"
+    WIDGET_RECREATE = "RECREATE"
 
     # Updates (generic)
-    UPDATE_DELETE = 'DELETE'
-    UPDATE_MODIFY = 'MODIFY'
-    UPDATE_ADD = 'ADD'
-    UPDATE_SELECTION = 'SELECTION'
+    UPDATE_DELETE = "DELETE"
+    UPDATE_MODIFY = "MODIFY"
+    UPDATE_ADD = "ADD"
+    UPDATE_SELECTION = "SELECTION"
     # high level update (i.e. not item level but organisation of items)
-    UPDATE_STRUCTURE = 'STRUCTURE'
+    UPDATE_STRUCTURE = "STRUCTURE"
 
-    LISTENERS = {'avatar', 'nick', 'presence', 'profilePlugged', 'disconnect', 'gotMenus',
-                 'menu', 'notification', 'notificationsClear', 'progressFinished',
-                 'progressError'}
+    LISTENERS = {
+        "avatar",
+        "nick",
+        "presence",
+        "profilePlugged",
+        "disconnect",
+        "gotMenus",
+        "menu",
+        "notification",
+        "notificationsClear",
+        "progressFinished",
+        "progressError",
+    }
 
     # Notifications
-    NOTIFY_MESSAGE = 'MESSAGE'  # a message has been received
-    NOTIFY_MENTION = 'MENTION'  # user has been mentionned
-    NOTIFY_PROGRESS_END = 'PROGRESS_END'  # a progression has finised
-    NOTIFY_GENERIC = 'GENERIC'  # a notification which has not its own type
+    NOTIFY_MESSAGE = "MESSAGE"  # a message has been received
+    NOTIFY_MENTION = "MENTION"  # user has been mentionned
+    NOTIFY_PROGRESS_END = "PROGRESS_END"  # a progression has finised
+    NOTIFY_GENERIC = "GENERIC"  # a notification which has not its own type
     NOTIFY_ALL = (NOTIFY_MESSAGE, NOTIFY_MENTION, NOTIFY_PROGRESS_END, NOTIFY_GENERIC)
--- a/sat_frontends/quick_frontend/quick_app.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/quick_frontend/quick_app.py	Wed Jun 27 20:14:46 2018 +0200
@@ -18,6 +18,7 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 
 from sat.core.i18n import _
@@ -39,13 +40,17 @@
 
 try:
     # FIXME: to be removed when an acceptable solution is here
-    unicode('')  # XXX: unicode doesn't exist in pyjamas
-except (TypeError, AttributeError):  # Error raised is not the same depending on pyjsbuild options
+    unicode("")  # XXX: unicode doesn't exist in pyjamas
+except (
+    TypeError,
+    AttributeError,
+):  # Error raised is not the same depending on pyjsbuild options
     unicode = str
 
 
 class ProfileManager(object):
     """Class managing all data relative to one profile, and plugging in mechanism"""
+
     # TODO: handle waiting XMLUI requests: getWaitingConf doesn't exist anymore
     #       and a way to keep some XMLUI request between sessions is expected in backend
     host = None
@@ -69,8 +74,13 @@
     def plug(self):
         """Plug the profile to the host"""
         # we get the essential params
-        self.bridge.asyncGetParamA("JabberID", "Connection", profile_key=self.profile,
-                                   callback=self._plug_profile_jid, errback=self._getParamError)
+        self.bridge.asyncGetParamA(
+            "JabberID",
+            "Connection",
+            profile_key=self.profile,
+            callback=self._plug_profile_jid,
+            errback=self._getParamError,
+        )
 
     def _plug_profile_jid(self, jid_s):
         self.whoami = jid.JID(jid_s)  # resource might change after the connection
@@ -78,24 +88,40 @@
 
     def _autodisconnectEb(self, failure_):
         # XXX: we ignore error on this parameter, as Libervia can't access it
-        log.warning(_("Error while trying to get autodisconnect param, ignoring: {}").format(failure_))
+        log.warning(
+            _("Error while trying to get autodisconnect param, ignoring: {}").format(
+                failure_
+            )
+        )
         self._plug_profile_autodisconnect("false")
 
     def _plug_profile_isconnected(self, connected):
         self.connected = connected
-        self.bridge.asyncGetParamA("autodisconnect", "Connection", profile_key=self.profile,
-                                   callback=self._plug_profile_autodisconnect, errback=self._autodisconnectEb)
+        self.bridge.asyncGetParamA(
+            "autodisconnect",
+            "Connection",
+            profile_key=self.profile,
+            callback=self._plug_profile_autodisconnect,
+            errback=self._autodisconnectEb,
+        )
 
     def _plug_profile_autodisconnect(self, autodisconnect):
         if C.bool(autodisconnect):
             self._autodisconnect = True
-        self.bridge.asyncGetParamA("autoconnect", "Connection", profile_key=self.profile,
-                                   callback=self._plug_profile_autoconnect, errback=self._getParamError)
+        self.bridge.asyncGetParamA(
+            "autoconnect",
+            "Connection",
+            profile_key=self.profile,
+            callback=self._plug_profile_autoconnect,
+            errback=self._getParamError,
+        )
 
     def _plug_profile_autoconnect(self, value_str):
         autoconnect = C.bool(value_str)
         if autoconnect and not self.connected:
-            self.host.connect(self.profile, callback=lambda dummy: self._plug_profile_afterconnect())
+            self.host.connect(
+                self.profile, callback=lambda dummy: self._plug_profile_afterconnect()
+            )
         else:
             self._plug_profile_afterconnect()
 
@@ -103,7 +129,11 @@
         # Profile can be connected or not
         # we get cached data
         self.connected = True
-        self.host.bridge.getFeatures(profile_key=self.profile, callback=self._plug_profile_getFeaturesCb, errback=self._plug_profile_getFeaturesEb)
+        self.host.bridge.getFeatures(
+            profile_key=self.profile,
+            callback=self._plug_profile_getFeaturesCb,
+            errback=self._plug_profile_getFeaturesEb,
+        )
 
     def _plug_profile_getFeaturesEb(self, failure):
         log.error(u"Couldn't get features: {}".format(failure))
@@ -129,42 +159,60 @@
                 self.host.entityDataUpdatedHandler(entity_s, key, value, self.profile)
 
         if not self.connected:
-            self.host.setPresenceStatus(C.PRESENCE_UNAVAILABLE, '', profile=self.profile)
+            self.host.setPresenceStatus(C.PRESENCE_UNAVAILABLE, "", profile=self.profile)
         else:
 
             contact_list.fill()
             self.host.setPresenceStatus(profile=self.profile)
 
-            #The waiting subscription requests
-            self.bridge.getWaitingSub(self.profile, callback=self._plug_profile_gotWaitingSub)
+            # The waiting subscription requests
+            self.bridge.getWaitingSub(
+                self.profile, callback=self._plug_profile_gotWaitingSub
+            )
 
     def _plug_profile_gotWaitingSub(self, waiting_sub):
         for sub in waiting_sub:
             self.host.subscribeHandler(waiting_sub[sub], sub, self.profile)
 
-        self.bridge.mucGetRoomsJoined(self.profile, callback=self._plug_profile_gotRoomsJoined)
+        self.bridge.mucGetRoomsJoined(
+            self.profile, callback=self._plug_profile_gotRoomsJoined
+        )
 
     def _plug_profile_gotRoomsJoined(self, rooms_args):
-        #Now we open the MUC window where we already are:
+        # Now we open the MUC window where we already are:
         for room_args in rooms_args:
             self.host.mucRoomJoinedHandler(*room_args, profile=self.profile)
-        #Presence must be requested after rooms are filled
-        self.host.bridge.getPresenceStatuses(self.profile, callback=self._plug_profile_gotPresences)
+        # Presence must be requested after rooms are filled
+        self.host.bridge.getPresenceStatuses(
+            self.profile, callback=self._plug_profile_gotPresences
+        )
 
     def _plug_profile_gotPresences(self, presences):
         def gotEntityData(data, contact):
-            for key in ('avatar', 'nick'):
+            for key in ("avatar", "nick"):
                 if key in data:
-                    self.host.entityDataUpdatedHandler(contact, key, data[key], self.profile)
+                    self.host.entityDataUpdatedHandler(
+                        contact, key, data[key], self.profile
+                    )
 
         for contact in presences:
             for res in presences[contact]:
-                jabber_id = (u'%s/%s' % (jid.JID(contact).bare, res)) if res else contact
+                jabber_id = (u"%s/%s" % (jid.JID(contact).bare, res)) if res else contact
                 show = presences[contact][res][0]
                 priority = presences[contact][res][1]
                 statuses = presences[contact][res][2]
-                self.host.presenceUpdateHandler(jabber_id, show, priority, statuses, self.profile)
-            self.host.bridge.getEntityData(contact, ['avatar', 'nick'], self.profile, callback=lambda data, contact=contact: gotEntityData(data, contact), errback=lambda failure, contact=contact: log.debug(u"No cache data for {}".format(contact)))
+                self.host.presenceUpdateHandler(
+                    jabber_id, show, priority, statuses, self.profile
+                )
+            self.host.bridge.getEntityData(
+                contact,
+                ["avatar", "nick"],
+                self.profile,
+                callback=lambda data, contact=contact: gotEntityData(data, contact),
+                errback=lambda failure, contact=contact: log.debug(
+                    u"No cache data for {}".format(contact)
+                ),
+            )
 
         # At this point, profile should be fully plugged
         # and we launch frontend specific method
@@ -200,13 +248,15 @@
 
     def plug(self, profile):
         if profile in self._profiles:
-            raise exceptions.ConflictError('A profile of the name [{}] is already plugged'.format(profile))
+            raise exceptions.ConflictError(
+                "A profile of the name [{}] is already plugged".format(profile)
+            )
         self._profiles[profile] = ProfileManager(profile)
         self._profiles[profile].plug()
 
     def unplug(self, profile):
         if profile not in self._profiles:
-            raise ValueError('The profile [{}] is not plugged'.format(profile))
+            raise ValueError("The profile [{}] is not plugged".format(profile))
 
         # remove the contact list and its listener
         host = self._profiles[profile].host
@@ -220,6 +270,7 @@
 
 class QuickApp(object):
     """This class contain the main methods needed for the frontend"""
+
     MB_HANDLER = True  # Set to False if the frontend doesn't manage microblog
     AVATARS_HANDLER = True  # set to False if avatars are not used
 
@@ -234,9 +285,11 @@
         self.menus = quick_menus.QuickMenusManager(self)
         ProfileManager.host = self
         self.profiles = ProfilesManager()
-        self._plugs_in_progress = set() # profiles currently being plugged, used to (un)lock contact list updates
-        self.ready_profiles = set() # profiles which are connected and ready
-        self.signals_cache = {} # used to keep signal received between start of plug_profile and when the profile is actualy ready
+        self._plugs_in_progress = (
+            set()
+        )  # profiles currently being plugged, used to (un)lock contact list updates
+        self.ready_profiles = set()  # profiles which are connected and ready
+        self.signals_cache = {}  # used to keep signal received between start of plug_profile and when the profile is actualy ready
         self.contact_lists = quick_contact_list.QuickContactListHandler(self)
         self.widgets = quick_widgets.QuickWidgetsManager(self)
         if check_options is not None:
@@ -245,13 +298,17 @@
             self.options = None
 
         # widgets
-        self.selected_widget = None # widget currently selected (must be filled by frontend)
+        self.selected_widget = (
+            None
+        )  # widget currently selected (must be filled by frontend)
 
         # listeners
-        self._listeners = {} # key: listener type ("avatar", "selected", etc), value: list of callbacks
+        self._listeners = {}  # key: listener type ("avatar", "selected", etc), value: list of callbacks
 
         # triggers
-        self.trigger = trigger.TriggerManager()  # trigger are used to change the default behaviour
+        self.trigger = (
+            trigger.TriggerManager()
+        )  # trigger are used to change the default behaviour
 
         ## bridge ##
         self.bridge = bridge_factory()
@@ -274,8 +331,9 @@
         log.error(_(u"Can't get namespaces map: {msg}").format(msg=failure_))
 
     def onBridgeConnected(self):
-        self.bridge.namespacesGet(callback=self._namespacesGetCb,
-                                  errback=self._namespacesGetEb)
+        self.bridge.namespacesGet(
+            callback=self._namespacesGetCb, errback=self._namespacesGetEb
+        )
 
     def _bridgeCb(self):
         self.registerSignal("connected")
@@ -331,7 +389,9 @@
         """
         raise NotImplementedError
 
-    def registerSignal(self, function_name, handler=None, iface="core", with_profile=True):
+    def registerSignal(
+        self, function_name, handler=None, iface="core", with_profile=True
+    ):
         """Register a handler for a signal
 
         @param function_name (str): name of the signal to handle
@@ -339,15 +399,15 @@
         @param iface (str): interface of the bridge to use ('core' or 'plugin')
         @param with_profile (boolean): True if the signal concerns a specific profile, in that case the profile name has to be passed by the caller
         """
-        log.debug(u"registering signal {name}".format(name = function_name))
+        log.debug(u"registering signal {name}".format(name=function_name))
         if handler is None:
-            handler = getattr(self, "{}{}".format(function_name, 'Handler'))
+            handler = getattr(self, "{}{}".format(function_name, "Handler"))
         if not with_profile:
             self.bridge.register_signal(function_name, handler, iface)
             return
 
         def signalReceived(*args, **kwargs):
-            profile = kwargs.get('profile')
+            profile = kwargs.get("profile")
             if profile is None:
                 if not args:
                     raise exceptions.ProfileNotSetError
@@ -356,9 +416,12 @@
                 if not self.check_profile(profile):
                     if profile in self.profiles:
                         # profile is not ready but is in self.profiles, that's mean that it's being connecting and we need to cache the signal
-                        self.signals_cache.setdefault(profile, []).append((function_name, handler, args, kwargs))
+                        self.signals_cache.setdefault(profile, []).append(
+                            (function_name, handler, args, kwargs)
+                        )
                     return  # we ignore signal for profiles we don't manage
             handler(*args, **kwargs)
+
         self.bridge.register_signal(function_name, signalReceived, iface)
 
     def addListener(self, type_, callback, profiles_filter=None):
@@ -449,10 +512,13 @@
         # profile is ready, we can call send signals that where is cache
         cached_signals = self.signals_cache.pop(profile, [])
         for function_name, handler, args, kwargs in cached_signals:
-            log.debug(u"Calling cached signal [%s] with args %s and kwargs %s" % (function_name, args, kwargs))
+            log.debug(
+                u"Calling cached signal [%s] with args %s and kwargs %s"
+                % (function_name, args, kwargs)
+            )
             handler(*args, **kwargs)
 
-        self.callListeners('profilePlugged', profile=profile)
+        self.callListeners("profilePlugged", profile=profile)
         if not self._plugs_in_progress:
             self.contact_lists.lockUpdate(False)
 
@@ -460,24 +526,29 @@
         if not callback:
             callback = lambda dummy: None
         if not errback:
+
             def errback(failure):
                 log.error(_(u"Can't connect profile [%s]") % failure)
                 try:
                     module = failure.module
                 except AttributeError:
-                    module = ''
+                    module = ""
                 try:
                     message = failure.message
                 except AttributeError:
-                    message = 'error'
+                    message = "error"
                 try:
                     fullname = failure.fullname
                 except AttributeError:
-                    fullname = 'error'
-                if module.startswith('twisted.words.protocols.jabber') and failure.condition == "not-authorized":
+                    fullname = "error"
+                if (
+                    module.startswith("twisted.words.protocols.jabber")
+                    and failure.condition == "not-authorized"
+                ):
                     self.launchAction(C.CHANGE_XMPP_PASSWD_ID, {}, profile=profile)
                 else:
-                    self.showDialog(message, fullname, 'error')
+                    self.showDialog(message, fullname, "error")
+
         self.bridge.connect(profile, callback=callback, errback=errback)
 
     def plug_profiles(self, profiles):
@@ -527,7 +598,7 @@
         """called when the connection is closed"""
         log.debug(_("Disconnected"))
         self.contact_lists[profile].disconnect()
-        self.setPresenceStatus(C.PRESENCE_UNAVAILABLE, '', profile=profile)
+        self.setPresenceStatus(C.PRESENCE_UNAVAILABLE, "", profile=profile)
 
     def actionNewHandler(self, action_data, id_, security_limit, profile):
         self.actionManager(action_data, user_action=False, profile=profile)
@@ -537,10 +608,23 @@
         groups = list(groups)
         self.contact_lists[profile].setContact(entity, groups, attributes, in_roster=True)
 
-    def messageNewHandler(self, uid, timestamp, from_jid_s, to_jid_s, msg, subject, type_, extra, profile):
+    def messageNewHandler(
+        self, uid, timestamp, from_jid_s, to_jid_s, msg, subject, type_, extra, profile
+    ):
         from_jid = jid.JID(from_jid_s)
         to_jid = jid.JID(to_jid_s)
-        if not self.trigger.point("messageNewTrigger", uid, timestamp, from_jid, to_jid, msg, subject, type_, extra, profile=profile):
+        if not self.trigger.point(
+            "messageNewTrigger",
+            uid,
+            timestamp,
+            from_jid,
+            to_jid,
+            msg,
+            subject,
+            type_,
+            extra,
+            profile=profile,
+        ):
             return
 
         from_me = from_jid.bare == self.profiles[profile].whoami.bare
@@ -550,66 +634,137 @@
             # we avoid resource locking, but we must keep resource for private MUC messages
             target = target.bare
         # we want to be sure to have at least one QuickChat instance
-        self.widgets.getOrCreateWidget(quick_chat.QuickChat, target, type_=C.CHAT_ONE2ONE, on_new_widget=None, profile=profile)
+        self.widgets.getOrCreateWidget(
+            quick_chat.QuickChat,
+            target,
+            type_=C.CHAT_ONE2ONE,
+            on_new_widget=None,
+            profile=profile,
+        )
 
-        if not from_jid in contact_list and from_jid.bare != self.profiles[profile].whoami.bare:
-            #XXX: needed to show entities which haven't sent any
+        if (
+            not from_jid in contact_list
+            and from_jid.bare != self.profiles[profile].whoami.bare
+        ):
+            # XXX: needed to show entities which haven't sent any
             #     presence information and which are not in roster
             contact_list.setContact(from_jid)
 
         # we dispatch the message in the widgets
-        for widget in self.widgets.getWidgets(quick_chat.QuickChat, target=target, profiles=(profile,)):
-            widget.messageNew(uid, timestamp, from_jid, target, msg, subject, type_, extra, profile)
+        for widget in self.widgets.getWidgets(
+            quick_chat.QuickChat, target=target, profiles=(profile,)
+        ):
+            widget.messageNew(
+                uid, timestamp, from_jid, target, msg, subject, type_, extra, profile
+            )
 
     def messageStateHandler(self, uid, status, profile):
         for widget in self.widgets.getWidgets(quick_chat.QuickChat, profiles=(profile,)):
             widget.onMessageState(uid, status, profile)
 
-    def messageSend(self, to_jid, message, subject=None, mess_type="auto", extra=None, callback=None, errback=None, profile_key=C.PROF_KEY_NONE):
+    def messageSend(
+        self,
+        to_jid,
+        message,
+        subject=None,
+        mess_type="auto",
+        extra=None,
+        callback=None,
+        errback=None,
+        profile_key=C.PROF_KEY_NONE,
+    ):
         if subject is None:
             subject = {}
         if extra is None:
             extra = {}
         if callback is None:
-            callback = lambda dummy=None: None # FIXME: optional argument is here because pyjamas doesn't support callback without arg with json proxy
+            callback = (
+                lambda dummy=None: None
+            )  # FIXME: optional argument is here because pyjamas doesn't support callback without arg with json proxy
         if errback is None:
-            errback = lambda failure: self.showDialog(failure.fullname, failure.message, "error")
+            errback = lambda failure: self.showDialog(
+                failure.fullname, failure.message, "error"
+            )
 
-        if not self.trigger.point("messageSendTrigger", to_jid, message, subject, mess_type, extra, callback, errback, profile_key=profile_key):
+        if not self.trigger.point(
+            "messageSendTrigger",
+            to_jid,
+            message,
+            subject,
+            mess_type,
+            extra,
+            callback,
+            errback,
+            profile_key=profile_key,
+        ):
             return
 
-        self.bridge.messageSend(unicode(to_jid), message, subject, mess_type, extra, profile_key, callback=callback, errback=errback)
+        self.bridge.messageSend(
+            unicode(to_jid),
+            message,
+            subject,
+            mess_type,
+            extra,
+            profile_key,
+            callback=callback,
+            errback=errback,
+        )
 
-    def setPresenceStatus(self, show='', status=None, profile=C.PROF_KEY_NONE):
+    def setPresenceStatus(self, show="", status=None, profile=C.PROF_KEY_NONE):
         raise NotImplementedError
 
     def presenceUpdateHandler(self, entity_s, show, priority, statuses, profile):
-        log.debug(_(u"presence update for %(entity)s (show=%(show)s, priority=%(priority)s, statuses=%(statuses)s) [profile:%(profile)s]")
-                  % {'entity': entity_s, C.PRESENCE_SHOW: show, C.PRESENCE_PRIORITY: priority, C.PRESENCE_STATUSES: statuses, 'profile': profile})
+        log.debug(
+            _(
+                u"presence update for %(entity)s (show=%(show)s, priority=%(priority)s, statuses=%(statuses)s) [profile:%(profile)s]"
+            )
+            % {
+                "entity": entity_s,
+                C.PRESENCE_SHOW: show,
+                C.PRESENCE_PRIORITY: priority,
+                C.PRESENCE_STATUSES: statuses,
+                "profile": profile,
+            }
+        )
         entity = jid.JID(entity_s)
 
         if entity == self.profiles[profile].whoami:
             if show == C.PRESENCE_UNAVAILABLE:
-                self.setPresenceStatus(C.PRESENCE_UNAVAILABLE, '', profile=profile)
+                self.setPresenceStatus(C.PRESENCE_UNAVAILABLE, "", profile=profile)
             else:
                 # FIXME: try to retrieve user language status before fallback to default
                 status = statuses.get(C.PRESENCE_STATUSES_DEFAULT, None)
                 self.setPresenceStatus(show, status, profile=profile)
             return
 
-        self.callListeners('presence', entity, show, priority, statuses, profile=profile)
+        self.callListeners("presence", entity, show, priority, statuses, profile=profile)
 
     def mucRoomJoinedHandler(self, room_jid_s, occupants, user_nick, subject, profile):
         """Called when a MUC room is joined"""
-        log.debug(u"Room [{room_jid}] joined by {profile}, users presents:{users}".format(room_jid=room_jid_s, profile=profile, users=occupants.keys()))
+        log.debug(
+            u"Room [{room_jid}] joined by {profile}, users presents:{users}".format(
+                room_jid=room_jid_s, profile=profile, users=occupants.keys()
+            )
+        )
         room_jid = jid.JID(room_jid_s)
-        self.widgets.getOrCreateWidget(quick_chat.QuickChat, room_jid, type_=C.CHAT_GROUP, nick=user_nick, occupants=occupants, subject=subject, profile=profile)
+        self.widgets.getOrCreateWidget(
+            quick_chat.QuickChat,
+            room_jid,
+            type_=C.CHAT_GROUP,
+            nick=user_nick,
+            occupants=occupants,
+            subject=subject,
+            profile=profile,
+        )
         self.contact_lists[profile].setSpecial(room_jid, C.CONTACT_SPECIAL_GROUP)
         # chat_widget.update()
 
     def mucRoomLeftHandler(self, room_jid_s, profile):
         """Called when a MUC room is left"""
-        log.debug(u"Room [%(room_jid)s] left by %(profile)s" % {'room_jid': room_jid_s, 'profile': profile})
+        log.debug(
+            u"Room [%(room_jid)s] left by %(profile)s"
+            % {"room_jid": room_jid_s, "profile": profile}
+        )
         room_jid = jid.JID(room_jid_s)
         chat_widget = self.widgets.getWidget(quick_chat.QuickChat, room_jid, profile)
         if chat_widget:
@@ -619,16 +774,26 @@
     def mucRoomUserChangedNickHandler(self, room_jid_s, old_nick, new_nick, profile):
         """Called when an user joined a MUC room"""
         room_jid = jid.JID(room_jid_s)
-        chat_widget = self.widgets.getOrCreateWidget(quick_chat.QuickChat, room_jid, type_=C.CHAT_GROUP, profile=profile)
+        chat_widget = self.widgets.getOrCreateWidget(
+            quick_chat.QuickChat, room_jid, type_=C.CHAT_GROUP, profile=profile
+        )
         chat_widget.changeUserNick(old_nick, new_nick)
-        log.debug(u"user [%(old_nick)s] is now known as [%(new_nick)s] in room [%(room_jid)s]" % {'old_nick': old_nick, 'new_nick': new_nick, 'room_jid': room_jid})
+        log.debug(
+            u"user [%(old_nick)s] is now known as [%(new_nick)s] in room [%(room_jid)s]"
+            % {"old_nick": old_nick, "new_nick": new_nick, "room_jid": room_jid}
+        )
 
     def mucRoomNewSubjectHandler(self, room_jid_s, subject, profile):
         """Called when subject of MUC room change"""
         room_jid = jid.JID(room_jid_s)
-        chat_widget = self.widgets.getOrCreateWidget(quick_chat.QuickChat, room_jid, type_=C.CHAT_GROUP, profile=profile)
+        chat_widget = self.widgets.getOrCreateWidget(
+            quick_chat.QuickChat, room_jid, type_=C.CHAT_GROUP, profile=profile
+        )
         chat_widget.setSubject(subject)
-        log.debug(u"new subject for room [%(room_jid)s]: %(subject)s" % {'room_jid': room_jid, "subject": subject})
+        log.debug(
+            u"new subject for room [%(room_jid)s]: %(subject)s"
+            % {"room_jid": room_jid, "subject": subject}
+        )
 
     def chatStateReceivedHandler(self, from_jid_s, state, profile):
         """Called when a new chat state (XEP-0085) is received.
@@ -641,7 +806,17 @@
         for widget in self.widgets.getWidgets(quick_chat.QuickChat, profiles=(profile,)):
             widget.onChatState(from_jid, state, profile)
 
-    def notify(self, type_, entity=None, message=None, subject=None, callback=None, cb_args=None, widget=None, profile=C.PROF_KEY_NONE):
+    def notify(
+        self,
+        type_,
+        entity=None,
+        message=None,
+        subject=None,
+        callback=None,
+        cb_args=None,
+        widget=None,
+        profile=C.PROF_KEY_NONE,
+    ):
         """Trigger an event notification
 
         @param type_(unicode): notifation kind,
@@ -656,22 +831,22 @@
         """
         assert type_ in C.NOTIFY_ALL
         notif_dict = self.profiles[profile].notifications
-        key = '' if entity is None else entity.bare
+        key = "" if entity is None else entity.bare
         type_notifs = notif_dict.setdefault(key, {}).setdefault(type_, [])
         notif_data = {
-            'id': self._notif_id,
-            'time': time.time(),
-            'entity': entity,
-            'callback': callback,
-            'cb_args': cb_args,
-            'message': message,
-            'subject': subject,
-            }
+            "id": self._notif_id,
+            "time": time.time(),
+            "entity": entity,
+            "callback": callback,
+            "cb_args": cb_args,
+            "message": message,
+            "subject": subject,
+        }
         if widget is not None:
             notif_data[widget] = widget
         type_notifs.append(notif_data)
         self._notifications[self._notif_id] = notif_data
-        self.callListeners('notification', entity, notif_data, profile=profile)
+        self.callListeners("notification", entity, notif_data, profile=profile)
 
     def getNotifs(self, entity=None, type_=None, exact_jid=None, profile=C.PROF_KEY_NONE):
         """return notifications for given entity
@@ -696,7 +871,7 @@
             exact_jid = False
         else:
             if entity is None:
-                key = ''
+                key = ""
                 exact_jid = False
             else:
                 key = entity.bare
@@ -713,7 +888,7 @@
 
             for notifs in type_notifs:
                 for notif in notifs:
-                    if exact_jid and notif['entity'] != entity:
+                    if exact_jid and notif["entity"] != entity:
                         continue
                     yield notif
 
@@ -727,7 +902,7 @@
         @return (list[dict]): list of notifications
         """
         notif_dict = self.profiles[profile].notifications
-        key = '' if entity is None else entity.bare
+        key = "" if entity is None else entity.bare
         try:
             if type_ is None:
                 del notif_dict[key]
@@ -735,7 +910,7 @@
                 del notif_dict[key][type_]
         except KeyError:
             return
-        self.callListeners('notificationsClear', entity, type_, profile=profile)
+        self.callListeners("notificationsClear", entity, type_, profile=profile)
 
     def psEventHandler(self, category, service_s, node, event_type, data, profile):
         """Called when a PubSub event is received.
@@ -750,23 +925,35 @@
 
         if category == C.PS_MICROBLOG and self.MB_HANDLER:
             if event_type == C.PS_PUBLISH:
-                if not 'content' in data:
+                if not "content" in data:
                     log.warning("No content found in microblog data")
                     return
-                _groups = set(data_format.dict2iter('group', data)) or None  # FIXME: check if [] make sense (instead of None)
+                _groups = (
+                    set(data_format.dict2iter("group", data)) or None
+                )  # FIXME: check if [] make sense (instead of None)
 
                 for wid in self.widgets.getWidgets(quick_blog.QuickBlog):
                     wid.addEntryIfAccepted(service_s, node, data, _groups, profile)
 
                 try:
-                    comments_node, comments_service = data['comments_node'], data['comments_service']
+                    comments_node, comments_service = (
+                        data["comments_node"],
+                        data["comments_service"],
+                    )
                 except KeyError:
                     pass
                 else:
-                    self.bridge.mbGet(comments_service, comments_node, C.NO_LIMIT, [], {"subscribe":C.BOOL_TRUE}, profile=profile)
+                    self.bridge.mbGet(
+                        comments_service,
+                        comments_node,
+                        C.NO_LIMIT,
+                        [],
+                        {"subscribe": C.BOOL_TRUE},
+                        profile=profile,
+                    )
             elif event_type == C.PS_RETRACT:
                 for wid in self.widgets.getWidgets(quick_blog.QuickBlog):
-                    wid.deleteEntryIfPresent(service_s, node, data['id'], profile)
+                    wid.deleteEntryIfPresent(service_s, node, data["id"], profile)
                 pass
             else:
                 log.warning("Unmanaged PubSub event type {}".format(event_type))
@@ -776,11 +963,11 @@
 
     def progressFinishedHandler(self, pid, metadata, profile):
         log.info(u"Progress {} finished".format(pid))
-        self.callListeners('progressFinished', pid, metadata, profile=profile)
+        self.callListeners("progressFinished", pid, metadata, profile=profile)
 
     def progressErrorHandler(self, pid, err_msg, profile):
         log.warning(u"Progress {pid} error: {err_msg}".format(pid=pid, err_msg=err_msg))
-        self.callListeners('progressError', pid, err_msg, profile=profile)
+        self.callListeners("progressError", pid, err_msg, profile=profile)
 
     def _subscribe_cb(self, answer, data):
         entity, profile = data
@@ -793,23 +980,34 @@
         if type == "subscribed":
             # this is a subscription confirmation, we just have to inform user
             # TODO: call self.getEntityMBlog to add the new contact blogs
-            self.showDialog(_(u"The contact {contact} has accepted your subscription")
-                            .format(contact=entity.bare), _(u'Subscription confirmation'))
+            self.showDialog(
+                _(u"The contact {contact} has accepted your subscription").format(
+                    contact=entity.bare
+                ),
+                _(u"Subscription confirmation"),
+            )
         elif type == "unsubscribed":
             # this is a subscription refusal, we just have to inform user
-            self.showDialog(_(u"The contact {contact} has refused your subscription")
-                              .format(contact=entity.bare),
-                            _(u'Subscription refusal'),
-                            'error')
+            self.showDialog(
+                _(u"The contact {contact} has refused your subscription").format(
+                    contact=entity.bare
+                ),
+                _(u"Subscription refusal"),
+                "error",
+            )
         elif type == "subscribe":
             # this is a subscriptionn request, we have to ask for user confirmation
             # TODO: use sat.stdui.ui_contact_list to display the groups selector
-            self.showDialog(_(u"The contact {contact} wants to subscribe to your presence"
-                              u".\nDo you accept ?").format(contact=entity.bare),
-                              _('Subscription confirmation'),
-                              'yes/no',
-                              answer_cb=self._subscribe_cb,
-                              answer_data=(entity, profile))
+            self.showDialog(
+                _(
+                    u"The contact {contact} wants to subscribe to your presence"
+                    u".\nDo you accept ?"
+                ).format(contact=entity.bare),
+                _("Subscription confirmation"),
+                "yes/no",
+                answer_cb=self._subscribe_cb,
+                answer_data=(entity, profile),
+            )
 
     def showDialog(self, message, title, type="info", answer_cb=None, answer_data=None):
         """Show a dialog to user
@@ -832,7 +1030,7 @@
 
     def showAlert(self, message):
         # FIXME: doesn't seems used anymore, to remove?
-        pass  #FIXME
+        pass  # FIXME
 
     def dialogFailure(self, failure):
         log.warning(u"Failure: {}".format(failure))
@@ -849,13 +1047,16 @@
         raise NotImplementedError
 
     def paramUpdateHandler(self, name, value, namespace, profile):
-        log.debug(_(u"param update: [%(namespace)s] %(name)s = %(value)s") % {'namespace': namespace, 'name': name, 'value': value})
+        log.debug(
+            _(u"param update: [%(namespace)s] %(name)s = %(value)s")
+            % {"namespace": namespace, "name": name, "value": value}
+        )
         if (namespace, name) == ("Connection", "JabberID"):
             log.debug(_(u"Changing JID to %s") % value)
             self.profiles[profile].whoami = jid.JID(value)
-        elif (namespace, name) == ('General', C.SHOW_OFFLINE_CONTACTS):
+        elif (namespace, name) == ("General", C.SHOW_OFFLINE_CONTACTS):
             self.contact_lists[profile].showOfflineContacts(C.bool(value))
-        elif (namespace, name) == ('General', C.SHOW_EMPTY_GROUPS):
+        elif (namespace, name) == ("General", C.SHOW_EMPTY_GROUPS):
             self.contact_lists[profile].showEmptyGroups(C.bool(value))
 
     def contactDeletedHandler(self, jid_s, profile):
@@ -866,13 +1067,20 @@
         entity = jid.JID(entity_s)
         if key == "nick":  # this is the roster nick, not the MUC nick
             if entity in self.contact_lists[profile]:
-                self.contact_lists[profile].setCache(entity, 'nick', value)
-                self.callListeners('nick', entity, value, profile=profile)
+                self.contact_lists[profile].setCache(entity, "nick", value)
+                self.callListeners("nick", entity, value, profile=profile)
         elif key == "avatar" and self.AVATARS_HANDLER:
             if value and entity in self.contact_lists[profile]:
                 self.getAvatar(entity, ignore_cache=True, profile=profile)
 
-    def actionManager(self, action_data, callback=None, ui_show_cb=None, user_action=True, profile=C.PROF_KEY_NONE):
+    def actionManager(
+        self,
+        action_data,
+        callback=None,
+        ui_show_cb=None,
+        user_action=True,
+        profile=C.PROF_KEY_NONE,
+    ):
         """Handle backend action
 
         @param action_data(dict): action dict as sent by launchAction or returned by an UI action
@@ -882,29 +1090,40 @@
             else the action come from backend direclty (i.e. actionNew)
         """
         try:
-            xmlui = action_data.pop('xmlui')
+            xmlui = action_data.pop("xmlui")
         except KeyError:
             pass
         else:
-            ui = self.xmlui.create(self, xml_data=xmlui, flags=("FROM_BACKEND",) if not user_action else None, callback=callback, profile=profile)
+            ui = self.xmlui.create(
+                self,
+                xml_data=xmlui,
+                flags=("FROM_BACKEND",) if not user_action else None,
+                callback=callback,
+                profile=profile,
+            )
             if ui_show_cb is None:
                 ui.show()
             else:
                 ui_show_cb(ui)
 
         try:
-            progress_id = action_data.pop('progress')
+            progress_id = action_data.pop("progress")
         except KeyError:
             pass
         else:
             self.progressIdHandler(progress_id, profile)
 
         # we ignore metadata
-        action_data = {k:v for k,v in action_data.iteritems() if not k.startswith("meta_")}
+        action_data = {
+            k: v for k, v in action_data.iteritems() if not k.startswith("meta_")
+        }
 
         if action_data:
-            raise exceptions.DataError(u"Not all keys in action_data are managed ({keys})".format(keys=', '.join(action_data.keys())))
-
+            raise exceptions.DataError(
+                u"Not all keys in action_data are managed ({keys})".format(
+                    keys=", ".join(action_data.keys())
+                )
+            )
 
     def _actionCb(self, data, callback, callback_id, profile):
         if callback is None:
@@ -912,7 +1131,9 @@
         else:
             callback(data=data, cb_id=callback_id, profile=profile)
 
-    def launchAction(self, callback_id, data=None, callback=None, profile=C.PROF_KEY_NONE):
+    def launchAction(
+        self, callback_id, data=None, callback=None, profile=C.PROF_KEY_NONE
+    ):
         """Launch a dynamic action
 
         @param callback_id: id of the action to launch
@@ -929,9 +1150,19 @@
         if data is None:
             data = dict()
         action_cb = lambda data: self._actionCb(data, callback, callback_id, profile)
-        self.bridge.launchAction(callback_id, data, profile, callback=action_cb, errback=self.dialogFailure)
+        self.bridge.launchAction(
+            callback_id, data, profile, callback=action_cb, errback=self.dialogFailure
+        )
 
-    def launchMenu(self, menu_type, path, data=None, callback=None, security_limit=C.SECURITY_LIMIT_MAX, profile=C.PROF_KEY_NONE):
+    def launchMenu(
+        self,
+        menu_type,
+        path,
+        data=None,
+        callback=None,
+        security_limit=C.SECURITY_LIMIT_MAX,
+        profile=C.PROF_KEY_NONE,
+    ):
         """Launch a menu manually
 
         @param menu_type(unicode): type of the menu to launch
@@ -948,19 +1179,36 @@
         """
         if data is None:
             data = dict()
-        action_cb = lambda data: self._actionCb(data, callback, (menu_type, path), profile)
-        self.bridge.menuLaunch(menu_type, path, data, security_limit, profile, callback=action_cb, errback=self.dialogFailure)
+        action_cb = lambda data: self._actionCb(
+            data, callback, (menu_type, path), profile
+        )
+        self.bridge.menuLaunch(
+            menu_type,
+            path,
+            data,
+            security_limit,
+            profile,
+            callback=action_cb,
+            errback=self.dialogFailure,
+        )
 
     def _avatarGetCb(self, avatar_path, entity, contact_list, profile):
         path = avatar_path or self.getDefaultAvatar(entity)
         contact_list.setCache(entity, "avatar", path)
-        self.callListeners('avatar', entity, path, profile=profile)
+        self.callListeners("avatar", entity, path, profile=profile)
 
     def _avatarGetEb(self, failure, entity, contact_list):
         log.warning(u"Can't get avatar: {}".format(failure))
         contact_list.setCache(entity, "avatar", self.getDefaultAvatar(entity))
 
-    def getAvatar(self, entity, cache_only=True, hash_only=False, ignore_cache=False, profile=C.PROF_KEY_NONE):
+    def getAvatar(
+        self,
+        entity,
+        cache_only=True,
+        hash_only=False,
+        ignore_cache=False,
+        profile=C.PROF_KEY_NONE,
+    ):
         """return avatar path for an entity
 
         @param entity(jid.JID): entity to get avatar from
@@ -982,8 +1230,11 @@
                 cache_only,
                 hash_only,
                 profile=profile,
-                callback=lambda path: self._avatarGetCb(path, entity, contact_list, profile),
-                errback=lambda failure: self._avatarGetEb(failure, entity, contact_list))
+                callback=lambda path: self._avatarGetCb(
+                    path, entity, contact_list, profile
+                ),
+                errback=lambda failure: self._avatarGetEb(failure, entity, contact_list),
+            )
             # we set avatar to empty string to avoid requesting several time the same avatar
             # while we are waiting for avatarGet result
             contact_list.setCache(entity, "avatar", "")
@@ -999,7 +1250,7 @@
 
     def disconnect(self, profile):
         log.info("disconnecting")
-        self.callListeners('disconnect', profile=profile)
+        self.callListeners("disconnect", profile=profile)
         self.bridge.disconnect(profile)
 
     def onExit(self):
@@ -1007,7 +1258,7 @@
         to_unplug = []
         for profile, profile_manager in self.profiles.iteritems():
             if profile_manager.connected and profile_manager.autodisconnect:
-                #The user wants autodisconnection
+                # The user wants autodisconnection
                 self.disconnect(profile)
             to_unplug.append(profile)
         for profile in to_unplug:
--- a/sat_frontends/quick_frontend/quick_blog.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/quick_frontend/quick_blog.py	Wed Jun 27 20:14:46 2018 +0200
@@ -19,6 +19,7 @@
 
 # from sat.core.i18n import _, D_
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 
 
@@ -29,8 +30,11 @@
 
 try:
     # FIXME: to be removed when an acceptable solution is here
-    unicode('') # XXX: unicode doesn't exist in pyjamas
-except (TypeError, AttributeError): # Error raised is not the same depending on pyjsbuild options
+    unicode("")  # XXX: unicode doesn't exist in pyjamas
+except (
+    TypeError,
+    AttributeError,
+):  # Error raised is not the same depending on pyjsbuild options
     unicode = str
 
 ENTRY_CLS = None
@@ -45,42 +49,46 @@
         @param data(dict): microblog data as return by bridge methods
             if data values are not defined, set default values
         """
-        self.id = data['id']
-        self.title = data.get('title')
+        self.id = data["id"]
+        self.title = data.get("title")
         self.title_rich = None
-        self.title_xhtml = data.get('title_xhtml')
-        self.tags = list(data_format.dict2iter('tag', data))
-        self.content = data.get('content')
+        self.title_xhtml = data.get("title_xhtml")
+        self.tags = list(data_format.dict2iter("tag", data))
+        self.content = data.get("content")
         self.content_rich = None
-        self.content_xhtml = data.get('content_xhtml')
-        self.author = data['author']
+        self.content_xhtml = data.get("content_xhtml")
+        self.author = data["author"]
         try:
-            author_jid = data['author_jid']
+            author_jid = data["author_jid"]
             self.author_jid = jid.JID(author_jid) if author_jid else None
         except KeyError:
             self.author_jid = None
 
         try:
-            self.author_verified = C.bool(data['author_jid_verified'])
+            self.author_verified = C.bool(data["author_jid_verified"])
         except KeyError:
             self.author_verified = False
 
         try:
-            self.updated = float(data['updated'])  # XXX: int doesn't work here (pyjamas bug)
+            self.updated = float(
+                data["updated"]
+            )  # XXX: int doesn't work here (pyjamas bug)
         except KeyError:
             self.updated = None
 
         try:
-            self.published = float(data['published'])  # XXX: int doesn't work here (pyjamas bug)
+            self.published = float(
+                data["published"]
+            )  # XXX: int doesn't work here (pyjamas bug)
         except KeyError:
             self.published = None
 
-        self.comments = data.get('comments')
+        self.comments = data.get("comments")
         try:
-            self.comments_service = jid.JID(data['comments_service'])
+            self.comments_service = jid.JID(data["comments_service"])
         except KeyError:
             self.comments_service = None
-        self.comments_node = data.get('comments_node')
+        self.comments_node = data.get("comments_node")
 
     # def loadComments(self):
     #     """Load all the comments"""
@@ -140,7 +148,16 @@
         for item, comments in items:
             self.addEntry(item, comments, service=service, node=node, with_update=False)
 
-    def addEntry(self, item=None, comments=None, service=None, node=None, with_update=True, editable=False, edit_entry=False):
+    def addEntry(
+        self,
+        item=None,
+        comments=None,
+        service=None,
+        node=None,
+        with_update=True,
+        editable=False,
+        edit_entry=False,
+    ):
         """Add a microblog entry
 
         @param editable (bool): True if the entry can be modified
@@ -178,7 +195,9 @@
     """Graphical representation of an Item
     This class must be overriden by frontends"""
 
-    def __init__(self, manager, item_data=None, comments_data=None, service=None, node=None):
+    def __init__(
+        self, manager, item_data=None, comments_data=None, service=None, node=None
+    ):
         """
         @param blog(QuickBlog): the parent QuickBlog
         @param manager(EntriesManager): the parent EntriesManager
@@ -194,7 +213,7 @@
         self.blog.id2entries[self.item.id] = self
         if self.item.comments:
             node_tuple = (self.item.comments_service, self.item.comments_node)
-            self.blog.node2entries.setdefault(node_tuple,[]).append(self)
+            self.blog.node2entries.setdefault(node_tuple, []).append(self)
 
     def reset(self, item_data):
         """Reset the entry with given data
@@ -205,17 +224,20 @@
         """
         if item_data is None:
             self.new = True
-            item_data = {'id': None,
-                         # TODO: find a best author value
-                         'author': self.blog.host.whoami.node
-                        }
+            item_data = {
+                "id": None,
+                # TODO: find a best author value
+                "author": self.blog.host.whoami.node,
+            }
         else:
             self.new = False
         self.item = Item(item_data)
-        self.author_jid =  self.blog.host.whoami.bare if self.new else self.item.author_jid
+        self.author_jid = self.blog.host.whoami.bare if self.new else self.item.author_jid
         if self.author_jid is None and self.service and self.service.node:
             self.author_jid = self.service
-        self.mode = C.ENTRY_MODE_TEXT if self.item.content_xhtml is None else C.ENTRY_MODE_XHTML
+        self.mode = (
+            C.ENTRY_MODE_TEXT if self.item.content_xhtml is None else C.ENTRY_MODE_XHTML
+        )
 
     def refresh(self):
         """Refresh the display when data have been modified"""
@@ -226,7 +248,7 @@
 
         @param editable(bool): True if the entry can be edited
         """
-        #XXX: we don't use @property as property setter doesn't play well with pyjamas
+        # XXX: we don't use @property as property setter doesn't play well with pyjamas
         raise NotImplementedError
 
     def addComments(self, comments_data):
@@ -249,7 +271,7 @@
 
         # keys to keep other than content*, title* and tag*
         # FIXME: see how to avoid comments node hijacking (someone could bind his post to another post's comments node)
-        keys_to_keep = ('id', 'comments', 'author', 'author_jid', 'published')
+        keys_to_keep = ("id", "comments", "author", "author_jid", "published")
 
         mb_data = {}
         for key in keys_to_keep:
@@ -257,14 +279,14 @@
             if value is not None:
                 mb_data[key] = unicode(value)
 
-        for prefix in ('content', 'title'):
-            for suffix in ('', '_rich', '_xhtml'):
-                name = '{}{}'.format(prefix, suffix)
+        for prefix in ("content", "title"):
+            for suffix in ("", "_rich", "_xhtml"):
+                name = "{}{}".format(prefix, suffix)
                 value = getattr(self.item, name)
                 if value is not None:
                     mb_data[name] = value
 
-        data_format.iter2dict('tag', self.item.tags, mb_data)
+        data_format.iter2dict("tag", self.item.tags, mb_data)
 
         if self.blog.new_message_target not in (C.PUBLIC, C.GROUP):
             raise NotImplementedError
@@ -273,13 +295,14 @@
             mb_data["allow_comments"] = C.BOOL_TRUE
 
         if self.blog.new_message_target == C.GROUP:
-            data_format.iter2dict('group', self.blog.targets, mb_data)
+            data_format.iter2dict("group", self.blog.targets, mb_data)
 
         self.blog.host.bridge.mbSend(
-            unicode(self.service or ''),
-            self.node or '',
+            unicode(self.service or ""),
+            self.node or "",
             mb_data,
-            profile=self.blog.profile)
+            profile=self.blog.profile,
+        )
 
     def delete(self):
         """Remove this Entry from parent manager
@@ -288,7 +311,7 @@
         all children entries will be recursively removed too
         """
         # XXX: named delete and not remove to avoid conflict with pyjamas
-        log.debug(u"deleting entry {}".format('EDIT ENTRY' if self.new else self.item.id))
+        log.debug(u"deleting entry {}".format("EDIT ENTRY" if self.new else self.item.id))
         for child in self.entries:
             child.delete()
         try:
@@ -303,8 +326,7 @@
             # in QuickBlog's dictionary
             del self.blog.id2entries[self.item.id]
             if self.item.comments:
-                comments_tuple = (self.item.comments_service,
-                                  self.item.comments_node)
+                comments_tuple = (self.item.comments_service, self.item.comments_node)
                 other_entries = self.blog.node2entries[comments_tuple].remove(self)
                 if not other_entries:
                     del self.blog.node2entries[comments_tuple]
@@ -316,12 +338,20 @@
         """
         # TODO: manage several comments nodes case.
         if self.item.comments:
-            self.blog.host.bridge.psNodeDelete(unicode(self.item.comments_service) or "", self.item.comments_node, profile=self.blog.profile)
-        self.blog.host.bridge.mbRetract(unicode(self.service or ""), self.node or "", self.item.id, profile=self.blog.profile)
+            self.blog.host.bridge.psNodeDelete(
+                unicode(self.item.comments_service) or "",
+                self.item.comments_node,
+                profile=self.blog.profile,
+            )
+        self.blog.host.bridge.mbRetract(
+            unicode(self.service or ""),
+            self.node or "",
+            self.item.id,
+            profile=self.blog.profile,
+        )
 
 
 class QuickBlog(EntriesManager, quick_widgets.QuickWidget):
-
     def __init__(self, host, targets, profiles=None):
         """Panel used to show microblog
 
@@ -330,12 +360,12 @@
             to know where to send new messages.
         """
         EntriesManager.__init__(self, None)
-        self.id2entries = {} # used to find an entry with it's item id
-                             # must be kept up-to-date by Entry
-        self.node2entries = {} # same as above, values are lists in case of
-                               # two entries link to the same comments node
+        self.id2entries = {}  # used to find an entry with it's item id
+        # must be kept up-to-date by Entry
+        self.node2entries = {}  # same as above, values are lists in case of
+        # two entries link to the same comments node
         if not targets:
-            targets = () # XXX: we use empty tuple instead of None to workaround a pyjamas bug
+            targets = ()  # XXX: we use empty tuple instead of None to workaround a pyjamas bug
             quick_widgets.QuickWidget.__init__(self, host, targets, C.PROF_KEY_NONE)
             self._targets_type = C.ALL
         else:
@@ -356,11 +386,17 @@
             raise ValueError("Unkown targets type")
 
     def __str__(self):
-        return u"Blog Widget [target: {}, profile: {}]".format(', '.join(self.targets), self.profile)
+        return u"Blog Widget [target: {}, profile: {}]".format(
+            ", ".join(self.targets), self.profile
+        )
 
     def _getResultsCb(self, data, rt_session):
         remaining, results = data
-        log.debug("Got {got_len} results, {rem_len} remaining".format(got_len=len(results), rem_len=remaining))
+        log.debug(
+            "Got {got_len} results, {rem_len} remaining".format(
+                got_len=len(results), rem_len=remaining
+            )
+        )
         for result in results:
             service, node, failure, items, metadata = result
             if not failure:
@@ -378,22 +414,46 @@
 
         @param rt_session(str): session id as returned by mbGetFromMany
         """
-        self.host.bridge.mbGetFromManyWithCommentsRTResult(rt_session, profile=self.profile,
-                                               callback=lambda data:self._getResultsCb(data, rt_session),
-                                               errback=self._getResultsEb)
+        self.host.bridge.mbGetFromManyWithCommentsRTResult(
+            rt_session,
+            profile=self.profile,
+            callback=lambda data: self._getResultsCb(data, rt_session),
+            errback=self._getResultsEb,
+        )
 
     def getAll(self):
         """Get all (micro)blogs from self.targets"""
+
         def gotSession(rt_session):
             self._getResults(rt_session)
 
         if self._targets_type in (C.ALL, C.GROUP):
             targets = tuple(self.targets) if self._targets_type is C.GROUP else ()
-            self.host.bridge.mbGetFromManyWithComments(self._targets_type, targets, 10, 10, {}, {"subscribe":C.BOOL_TRUE}, profile=self.profile, callback=gotSession)
+            self.host.bridge.mbGetFromManyWithComments(
+                self._targets_type,
+                targets,
+                10,
+                10,
+                {},
+                {"subscribe": C.BOOL_TRUE},
+                profile=self.profile,
+                callback=gotSession,
+            )
             own_pep = self.host.whoami.bare
-            self.host.bridge.mbGetFromManyWithComments(C.JID, (unicode(own_pep),), 10, 10, {}, {}, profile=self.profile, callback=gotSession)
+            self.host.bridge.mbGetFromManyWithComments(
+                C.JID,
+                (unicode(own_pep),),
+                10,
+                10,
+                {},
+                {},
+                profile=self.profile,
+                callback=gotSession,
+            )
         else:
-            raise NotImplementedError(u'{} target type is not managed'.format(self._targets_type))
+            raise NotImplementedError(
+                u"{} target type is not managed".format(self._targets_type)
+            )
 
     def isJidAccepted(self, jid_):
         """Tell if a jid is actepted and must be shown in this panel
@@ -403,7 +463,7 @@
         """
         if self._targets_type == C.ALL:
             return True
-        assert self._targets_type is C.GROUP # we don't manage other types for now
+        assert self._targets_type is C.GROUP  # we don't manage other types for now
         for group in self.targets:
             if self.host.contact_lists[self.profile].isEntityInGroup(jid_, group):
                 return True
@@ -422,7 +482,7 @@
         @param profile: %(doc_profile)s
         """
         try:
-            entry = self.id2entries[mb_data['id']]
+            entry = self.id2entries[mb_data["id"]]
         except KeyError:
             # The entry is new
             try:
@@ -430,9 +490,14 @@
             except:
                 # The node is unknown,
                 # we need to check that we can accept the entry
-                if (self.isJidAccepted(service)
-                    or (groups is None and service == self.host.profiles[self.profile].whoami.bare)
-                    or (groups and groups.intersection(self.targets))):
+                if (
+                    self.isJidAccepted(service)
+                    or (
+                        groups is None
+                        and service == self.host.profiles[self.profile].whoami.bare
+                    )
+                    or (groups and groups.intersection(self.targets))
+                ):
                     self.addEntry(mb_data, service=service, node=node)
             else:
                 # the entry is a comment in a known node
--- a/sat_frontends/quick_frontend/quick_chat.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/quick_frontend/quick_chat.py	Wed Jun 27 20:14:46 2018 +0200
@@ -19,6 +19,7 @@
 
 from sat.core.i18n import _
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from sat.core import exceptions
 from sat_frontends.quick_frontend import quick_widgets
@@ -26,31 +27,48 @@
 from collections import OrderedDict
 from sat_frontends.tools import jid
 import time
+
 try:
     from locale import getlocale
 except ImportError:
     # FIXME: pyjamas workaround
-    getlocale = lambda x: (None, 'utf-8')
+    getlocale = lambda x: (None, "utf-8")
 
 
-ROOM_USER_JOINED = 'ROOM_USER_JOINED'
-ROOM_USER_LEFT = 'ROOM_USER_LEFT'
+ROOM_USER_JOINED = "ROOM_USER_JOINED"
+ROOM_USER_LEFT = "ROOM_USER_LEFT"
 ROOM_USER_MOVED = (ROOM_USER_JOINED, ROOM_USER_LEFT)
 
 # from datetime import datetime
 
 try:
     # FIXME: to be removed when an acceptable solution is here
-    unicode('') # XXX: unicode doesn't exist in pyjamas
-except (TypeError, AttributeError): # Error raised is not the same depending on pyjsbuild options
+    unicode("")  # XXX: unicode doesn't exist in pyjamas
+except (
+    TypeError,
+    AttributeError,
+):  # Error raised is not the same depending on pyjsbuild options
     unicode = str
 
 # FIXME: day_format need to be settable (i18n)
 
+
 class Message(object):
     """Message metadata"""
 
-    def __init__(self, parent, uid, timestamp, from_jid, to_jid, msg, subject, type_, extra, profile):
+    def __init__(
+        self,
+        parent,
+        uid,
+        timestamp,
+        from_jid,
+        to_jid,
+        msg,
+        subject,
+        type_,
+        extra,
+        profile,
+    ):
         self.parent = parent
         self.profile = profile
         self.uid = uid
@@ -64,7 +82,11 @@
         self.nick = self.getNick(from_jid)
         self._status = None
         # own_mess is True if message was sent by profile's jid
-        self.own_mess = (from_jid.resource == self.parent.nick) if self.parent.type == C.CHAT_GROUP else (from_jid.bare == self.host.profiles[profile].whoami.bare)
+        self.own_mess = (
+            (from_jid.resource == self.parent.nick)
+            if self.parent.type == C.CHAT_GROUP
+            else (from_jid.bare == self.host.profiles[profile].whoami.bare)
+        )
         # is user mentioned here ?
         if self.parent.type == C.CHAT_GROUP and not self.own_mess:
             for m in msg.itervalues():
@@ -80,7 +102,7 @@
 
     @property
     def info_type(self):
-        return self.extra.get('info_type')
+        return self.extra.get("info_type")
 
     @property
     def mention(self):
@@ -92,7 +114,7 @@
     @property
     def history(self):
         """True if message come from history"""
-        return self.extra.get('history', False)
+        return self.extra.get("history", False)
 
     @property
     def main_message(self):
@@ -101,8 +123,8 @@
             self.selected_lang = self.parent.lang
             return self.message[self.parent.lang]
         try:
-            self.selected_lang = ''
-            return self.message['']
+            self.selected_lang = ""
+            return self.message[""]
         except KeyError:
             try:
                 lang, mess = self.message.iteritems().next()
@@ -110,24 +132,23 @@
                 return mess
             except StopIteration:
                 log.error(u"Can't find message for uid {}".format(self.uid))
-                return ''
+                return ""
 
     @property
     def main_message_xhtml(self):
         """rich message"""
-        xhtml = {k:v for k,v in self.extra.iteritems() if 'html' in k}
+        xhtml = {k: v for k, v in self.extra.iteritems() if "html" in k}
         if xhtml:
             # FIXME: we only return first found value for now
             return next(xhtml.itervalues())
 
-
     @property
     def time_text(self):
         """Return timestamp in a nicely formatted way"""
         # if the message was sent before today, we print the full date
         timestamp = time.localtime(self.timestamp)
         time_format = u"%c" if timestamp < self.parent.day_change else u"%H:%M"
-        return time.strftime(time_format, timestamp).decode(getlocale()[1] or 'utf-8')
+        return time.strftime(time_format, timestamp).decode(getlocale()[1] or "utf-8")
 
     @property
     def avatar(self):
@@ -140,15 +161,22 @@
         contact_list = self.host.contact_lists[self.profile]
         if self.type == C.MESS_TYPE_INFO and self.info_type in ROOM_USER_MOVED:
             try:
-                return self.extra['user_nick']
+                return self.extra["user_nick"]
             except KeyError:
                 log.error(u"extra data is missing user nick for uid {}".format(self.uid))
                 return ""
         # FIXME: converted getSpecials to list for pyjamas
-        if self.parent.type == C.CHAT_GROUP or entity in list(contact_list.getSpecials(C.CONTACT_SPECIAL_GROUP)):
+        if self.parent.type == C.CHAT_GROUP or entity in list(
+            contact_list.getSpecials(C.CONTACT_SPECIAL_GROUP)
+        ):
             return entity.resource or ""
         if entity.bare in contact_list:
-            return contact_list.getCache(entity, 'nick') or contact_list.getCache(entity, 'name') or entity.node or entity
+            return (
+                contact_list.getCache(entity, "nick")
+                or contact_list.getCache(entity, "name")
+                or entity.node
+                or entity
+            )
         return entity.node or entity
 
     @property
@@ -178,7 +206,7 @@
                 break
         if me:
             self.type = C.MESS_TYPE_INFO
-            self.extra['info_type'] = 'me'
+            self.extra["info_type"] = "me"
             nick = self.nick
             for lang, mess in self.message.iteritems():
                 self.message[lang] = u"* " + nick + mess[3:]
@@ -190,10 +218,10 @@
     def __init__(self, parent, data, profile):
         self.parent = parent
         self.profile = profile
-        self.nick = data['nick']
-        self._entity = data.get('entity')
-        self.affiliation = data['affiliation']
-        self.role = data['role']
+        self.nick = data["nick"]
+        self._entity = data.get("entity")
+        self.affiliation = data["affiliation"]
+        self.role = data["role"]
         self.widgets = set()  # widgets linked to this occupant
         self._state = None
 
@@ -201,11 +229,11 @@
     def data(self):
         """reconstruct data dict from attributes"""
         data = {}
-        data['nick'] = self.nick
+        data["nick"] = self.nick
         if self._entity is not None:
-            data['entity'] = self._entity
-        data['affiliation'] = self.affiliation
-        data['role'] = self.role
+            data["entity"] = self._entity
+        data["affiliation"] = self.affiliation
+        data["role"] = self.role
         return data
 
     @property
@@ -239,23 +267,34 @@
 
 
 class QuickChat(quick_widgets.QuickWidget):
-    visible_states = ['chat_state']  # FIXME: to be removed, used only in quick_games
+    visible_states = ["chat_state"]  # FIXME: to be removed, used only in quick_games
 
-    def __init__(self, host, target, type_=C.CHAT_ONE2ONE, nick=None, occupants=None, subject=None, profiles=None):
+    def __init__(
+        self,
+        host,
+        target,
+        type_=C.CHAT_ONE2ONE,
+        nick=None,
+        occupants=None,
+        subject=None,
+        profiles=None,
+    ):
         """
         @param type_: can be C.CHAT_ONE2ONE for single conversation or C.CHAT_GROUP for chat à la IRC
         """
-        self.lang = ''  # default language to use for messages
+        self.lang = ""  # default language to use for messages
         quick_widgets.QuickWidget.__init__(self, host, target, profiles=profiles)
-        self._locked = True   # True when we are waiting for history/search
-                              # messageNew signals are cached when locked
+        self._locked = True  # True when we are waiting for history/search
+        # messageNew signals are cached when locked
         self._cache = OrderedDict()
         assert type_ in (C.CHAT_ONE2ONE, C.CHAT_GROUP)
         self.current_target = target
         self.type = type_
         if type_ == C.CHAT_GROUP:
             if target.resource:
-                raise exceptions.InternalError(u"a group chat entity can't have a resource")
+                raise exceptions.InternalError(
+                    u"a group chat entity can't have a resource"
+                )
             if nick is None:
                 raise exceptions.InternalError(u"nick must not be None for group chat")
 
@@ -264,14 +303,26 @@
             self.setOccupants(occupants)
         else:
             if occupants is not None or nick is not None:
-                raise exceptions.InternalError(u"only group chat can have occupants or nick")
+                raise exceptions.InternalError(
+                    u"only group chat can have occupants or nick"
+                )
         self.messages = OrderedDict()  # key: uid, value: Message instance
         self.games = {}  # key=game name (unicode), value=instance of quick_games.RoomGame
         self.subject = subject
         lt = time.localtime()
-        self.day_change = (lt.tm_year, lt.tm_mon, lt.tm_mday, 0, 0, 0, lt.tm_wday, lt.tm_yday, lt.tm_isdst)  # struct_time of day changing time
+        self.day_change = (
+            lt.tm_year,
+            lt.tm_mon,
+            lt.tm_mday,
+            0,
+            0,
+            0,
+            lt.tm_wday,
+            lt.tm_yday,
+            lt.tm_isdst,
+        )  # struct_time of day changing time
         if self.host.AVATARS_HANDLER:
-            self.host.addListener('avatar', self.onAvatar, profiles)
+            self.host.addListener("avatar", self.onAvatar, profiles)
 
     def postInit(self):
         """Method to be called by frontend after widget is initialised
@@ -284,7 +335,7 @@
 
     def onDelete(self):
         if self.host.AVATARS_HANDLER:
-            self.host.removeListener('avatar', self.onAvatar)
+            self.host.removeListener("avatar", self.onAvatar)
 
     @property
     def contact_list(self):
@@ -293,7 +344,9 @@
     ## Widget management ##
 
     def __str__(self):
-        return u"Chat Widget [target: {}, type: {}, profile: {}]".format(self.target, self.type, self.profile)
+        return u"Chat Widget [target: {}, type: {}, profile: {}]".format(
+            self.target, self.type, self.profile
+        )
 
     @staticmethod
     def getWidgetHash(target, profiles):
@@ -311,16 +364,18 @@
     def addTarget(self, target):
         super(QuickChat, self).addTarget(target)
         if target.resource:
-            self.current_target = target # FIXME: tmp, must use resource priority throught contactList instead
+            self.current_target = (
+                target
+            )  # FIXME: tmp, must use resource priority throught contactList instead
 
     def recreateArgs(self, args, kwargs):
         """copy important attribute for a new widget"""
-        kwargs['type_'] = self.type
+        kwargs["type_"] = self.type
         if self.type == C.CHAT_GROUP:
-            kwargs['occupants'] = {o.nick: o.data for o in self.occupants.itervalues()}
-        kwargs['subject'] = self.subject
+            kwargs["occupants"] = {o.nick: o.data for o in self.occupants.itervalues()}
+        kwargs["subject"] = self.subject
         try:
-            kwargs['nick'] = self.nick
+            kwargs["nick"] = self.nick
         except AttributeError:
             pass
 
@@ -333,7 +388,14 @@
 
         @param entity: full jid of the target
         """
-        return self.host.widgets.getOrCreateWidget(QuickChat, entity, type_=C.CHAT_ONE2ONE, force_hash=self.getPrivateHash(self.profile, entity), on_new_widget=self.onPrivateCreated, profile=self.profile) # we force hash to have a new widget, not this one again
+        return self.host.widgets.getOrCreateWidget(
+            QuickChat,
+            entity,
+            type_=C.CHAT_ONE2ONE,
+            force_hash=self.getPrivateHash(self.profile, entity),
+            on_new_widget=self.onPrivateCreated,
+            profile=self.profile,
+        )  # we force hash to have a new widget, not this one again
 
     @property
     def target(self):
@@ -347,25 +409,17 @@
         """set the whole list of occupants"""
         assert len(self.occupants) == 0
         for nick, data in occupants.iteritems():
-            self.occupants[nick] = Occupant(
-                self,
-                data,
-                self.profile
-                )
+            self.occupants[nick] = Occupant(self, data, self.profile)
 
     def addUser(self, occupant_data):
         """Add user if it is not in the group list"""
-        occupant = Occupant(
-            self,
-            occupant_data,
-            self.profile
-            )
+        occupant = Occupant(self, occupant_data, self.profile)
         self.occupants[occupant.nick] = occupant
         return occupant
 
     def removeUser(self, occupant_data):
         """Remove a user from the group list"""
-        nick = occupant_data['nick']
+        nick = occupant_data["nick"]
         try:
             occupant = self.occupants.pop(nick)
         except KeyError:
@@ -391,14 +445,17 @@
         @return (bool): True if this Chat Widget manage this couple
         """
         if self.type == C.CHAT_GROUP:
-            if mess_type in (C.MESS_TYPE_GROUPCHAT, C.MESS_TYPE_INFO) and self.target == entity.bare:
+            if (
+                mess_type in (C.MESS_TYPE_GROUPCHAT, C.MESS_TYPE_INFO)
+                and self.target == entity.bare
+            ):
                 return True
         else:
             if mess_type != C.MESS_TYPE_GROUPCHAT and entity in self.targets:
                 return True
         return False
 
-    def updateHistory(self, size=C.HISTORY_LIMIT_DEFAULT, filters=None, profile='@NONE@'):
+    def updateHistory(self, size=C.HISTORY_LIMIT_DEFAULT, filters=None, profile="@NONE@"):
         """Called when history need to be recreated
 
         Remove all message from history then call historyPrint
@@ -423,7 +480,7 @@
             self.messageNew(*data)
         del self._cache
 
-    def historyPrint(self, size=C.HISTORY_LIMIT_DEFAULT, filters=None, profile='@NONE@'):
+    def historyPrint(self, size=C.HISTORY_LIMIT_DEFAULT, filters=None, profile="@NONE@"):
         """Print the current history
 
         @param size (int): number of messages
@@ -442,21 +499,23 @@
         log.debug(log_msg)
 
         if self.type == C.CHAT_ONE2ONE:
-            special = self.host.contact_lists[self.profile].getCache(self.target, C.CONTACT_SPECIAL)
+            special = self.host.contact_lists[self.profile].getCache(
+                self.target, C.CONTACT_SPECIAL
+            )
             if special == C.CONTACT_SPECIAL_GROUP:
                 # we have a private conversation
                 # so we need full jid for the history
                 # (else we would get history from group itself)
                 # and to filter out groupchat message
                 target = self.target
-                filters['not_types'] = C.MESS_TYPE_GROUPCHAT
+                filters["not_types"] = C.MESS_TYPE_GROUPCHAT
             else:
                 target = self.target.bare
         else:
             # groupchat
             target = self.target.bare
             # FIXME: info not handled correctly
-            filters['types'] = C.MESS_TYPE_GROUPCHAT
+            filters["types"] = C.MESS_TYPE_GROUPCHAT
 
         def _historyGetCb(history):
             # day_format = "%A, %d %b %Y"  # to display the day change
@@ -476,42 +535,80 @@
                 # if ((self.type == C.CHAT_GROUP and type_ != C.MESS_TYPE_GROUPCHAT) or
                 #    (self.type == C.CHAT_ONE2ONE and type_ == C.MESS_TYPE_GROUPCHAT)):
                 #     continue
-                extra['history'] = True
-                self.messages[uid] = Message(self, uid, timestamp, from_jid, to_jid, message, subject, type_, extra, profile)
+                extra["history"] = True
+                self.messages[uid] = Message(
+                    self,
+                    uid,
+                    timestamp,
+                    from_jid,
+                    to_jid,
+                    message,
+                    subject,
+                    type_,
+                    extra,
+                    profile,
+                )
             self._onHistoryPrinted()
 
         def _historyGetEb(err):
             log.error(_(u"Can't get history: {}").format(err))
             self._onHistoryPrinted()
 
-        self.host.bridge.historyGet(unicode(self.host.profiles[profile].whoami.bare), unicode(target), size, True, filters, profile, callback=_historyGetCb, errback=_historyGetEb)
+        self.host.bridge.historyGet(
+            unicode(self.host.profiles[profile].whoami.bare),
+            unicode(target),
+            size,
+            True,
+            filters,
+            profile,
+            callback=_historyGetCb,
+            errback=_historyGetEb,
+        )
 
-    def messageNew(self, uid, timestamp, from_jid, to_jid, msg, subject, type_, extra, profile):
+    def messageNew(
+        self, uid, timestamp, from_jid, to_jid, msg, subject, type_, extra, profile
+    ):
         if self._locked:
-            self._cache[uid] = (uid, timestamp, from_jid, to_jid, msg, subject, type_, extra, profile)
+            self._cache[uid] = (
+                uid,
+                timestamp,
+                from_jid,
+                to_jid,
+                msg,
+                subject,
+                type_,
+                extra,
+                profile,
+            )
             return
         if self.type == C.CHAT_GROUP:
             if to_jid.resource and type_ != C.MESS_TYPE_GROUPCHAT:
                 # we have a private message, we forward it to a private conversation widget
                 chat_widget = self.getOrCreatePrivateWidget(to_jid)
-                chat_widget.messageNew(uid, timestamp, from_jid, to_jid, msg, subject, type_, extra, profile)
+                chat_widget.messageNew(
+                    uid, timestamp, from_jid, to_jid, msg, subject, type_, extra, profile
+                )
                 return
             if type_ == C.MESS_TYPE_INFO:
                 try:
-                    info_type = extra['info_type']
+                    info_type = extra["info_type"]
                 except KeyError:
                     pass
                 else:
-                    user_data = {k[5:]:v for k,v in extra.iteritems() if k.startswith('user_')}
+                    user_data = {
+                        k[5:]: v for k, v in extra.iteritems() if k.startswith("user_")
+                    }
                     if info_type == ROOM_USER_JOINED:
                         self.addUser(user_data)
                     elif info_type == ROOM_USER_LEFT:
                         self.removeUser(user_data)
 
-        message = Message(self, uid, timestamp, from_jid, to_jid, msg, subject, type_, extra, profile)
+        message = Message(
+            self, uid, timestamp, from_jid, to_jid, msg, subject, type_, extra, profile
+        )
         self.messages[uid] = message
 
-        if 'received_timestamp' in extra:
+        if "received_timestamp" in extra:
             log.warning(u"Delayed message received after history, this should not happen")
         self.createMessage(message)
 
@@ -537,7 +634,9 @@
     def setSubject(self, subject):
         """Set title for a group chat"""
         if self.type != C.CHAT_GROUP:
-            raise exceptions.InternalError("trying to set subject for a non group chat window")
+            raise exceptions.InternalError(
+                "trying to set subject for a non group chat window"
+            )
         self.subject = subject
 
     def changeSubject(self, new_subject):
@@ -579,8 +678,11 @@
             try:
                 self.occupants[nick].state = state
             except KeyError:
-                log.warning(u"{nick} not found in {room}, ignoring new chat state".format(
-                    nick=nick, room=self.target.bare))
+                log.warning(
+                    u"{nick} not found in {room}, ignoring new chat state".format(
+                        nick=nick, room=self.target.bare
+                    )
+                )
 
     def onMessageState(self, uid, status, profile):
         try:
@@ -594,7 +696,7 @@
         if self.type == C.CHAT_GROUP:
             if entity.bare == self.target:
                 try:
-                    self.occupants[entity.resource].update({'avatar': filename})
+                    self.occupants[entity.resource].update({"avatar": filename})
                 except KeyError:
                     # can happen for a message in history where the
                     # entity is not here anymore
@@ -603,14 +705,17 @@
                 for m in self.messages.values():
                     if m.nick == entity.resource:
                         for w in m.widgets:
-                            w.update({'avatar': filename})
+                            w.update({"avatar": filename})
         else:
-            if entity.bare == self.target.bare or entity.bare == self.host.profiles[profile].whoami.bare:
+            if (
+                entity.bare == self.target.bare
+                or entity.bare == self.host.profiles[profile].whoami.bare
+            ):
                 log.info(u"avatar updated for {}".format(entity))
                 for m in self.messages.values():
                     if m.from_jid.bare == entity.bare:
                         for w in m.widgets:
-                            w.update({'avatar': filename})
+                            w.update({"avatar": filename})
 
 
 quick_widgets.register(QuickChat)
--- a/sat_frontends/quick_frontend/quick_contact_list.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/quick_frontend/quick_contact_list.py	Wed Jun 27 20:14:46 2018 +0200
@@ -22,6 +22,7 @@
 
 from sat.core.i18n import _
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from sat.core import exceptions
 from sat_frontends.quick_frontend.quick_widgets import QuickWidget
@@ -32,9 +33,9 @@
 
 try:
     # FIXME: to be removed when an acceptable solution is here
-    unicode('')  # XXX: unicode doesn't exist in pyjamas
+    unicode("")  # XXX: unicode doesn't exist in pyjamas
 except (TypeError, AttributeError):  # Error raised is not the same depending on
-                                     # pyjsbuild options
+    # pyjsbuild options
     # XXX: pyjamas' max doesn't support key argument, so we implement it ourself
     pyjamas_max = max
 
@@ -95,27 +96,31 @@
         # (e.g. not in contact list) if they have notifications attached
         self.show_entities_with_notifs = True
 
-        self.host.bridge.asyncGetParamA(C.SHOW_EMPTY_GROUPS,
-                                        "General",
-                                        profile_key=profile,
-                                        callback=self._showEmptyGroups)
+        self.host.bridge.asyncGetParamA(
+            C.SHOW_EMPTY_GROUPS,
+            "General",
+            profile_key=profile,
+            callback=self._showEmptyGroups,
+        )
 
-        self.host.bridge.asyncGetParamA(C.SHOW_OFFLINE_CONTACTS,
-                                        "General",
-                                        profile_key=profile,
-                                        callback=self._showOfflineContacts)
+        self.host.bridge.asyncGetParamA(
+            C.SHOW_OFFLINE_CONTACTS,
+            "General",
+            profile_key=profile,
+            callback=self._showOfflineContacts,
+        )
 
         # FIXME: workaround for a pyjamas issue: calling hash on a class method always
         #        return a different value if that method is defined directly within the
         #        class (with the "def" keyword)
         self.presenceListener = self.onPresenceUpdate
-        self.host.addListener('presence', self.presenceListener, [self.profile])
+        self.host.addListener("presence", self.presenceListener, [self.profile])
         self.nickListener = self.onNickUpdate
-        self.host.addListener('nick', self.nickListener, [self.profile])
+        self.host.addListener("nick", self.nickListener, [self.profile])
         self.notifListener = self.onNotification
-        self.host.addListener('notification', self.notifListener, [self.profile])
+        self.host.addListener("notification", self.notifListener, [self.profile])
         # notifListener only update the entity, so we can re-use it
-        self.host.addListener('notificationsClear', self.notifListener, [self.profile])
+        self.host.addListener("notificationsClear", self.notifListener, [self.profile])
 
     def _showEmptyGroups(self, show_str):
         # Called only by __init__
@@ -155,8 +160,13 @@
 
         @return (set[jid.JID])
         """
-        return set([entity for entity in self._roster
-            if self.getCache(entity, C.PRESENCE_SHOW) is not None])
+        return set(
+            [
+                entity
+                for entity in self._roster
+                if self.getCache(entity, C.PRESENCE_SHOW) is not None
+            ]
+        )
 
     @property
     def roster_entities_by_group(self):
@@ -165,7 +175,7 @@
         This also includes the empty group (None key).
         @return (dict[unicode,set(jid.JID)])
         """
-        return {group: self._groups[group]['jids'] for group in self._groups}
+        return {group: self._groups[group]["jids"] for group in self._groups}
 
     @property
     def roster_groups_by_entities(self):
@@ -175,7 +185,7 @@
         """
         result = {}
         for group, data in self._groups.iteritems():
-            for entity in data['jids']:
+            for entity in data["jids"]:
                 result.setdefault(entity, set()).add(group)
         return result
 
@@ -195,7 +205,6 @@
         """
         return self._cache.iteritems()
 
-
     @property
     def items(self):
         """Return item representation for all visible entities in cache
@@ -203,9 +212,11 @@
         entities are not sorted
         key: bare jid, value: data
         """
-        return {jid_:cache for jid_, cache in self._cache.iteritems()
-                if self.entityVisible(jid_)}
-
+        return {
+            jid_: cache
+            for jid_, cache in self._cache.iteritems()
+            if self.entityVisible(jid_)
+        }
 
     def getItem(self, entity):
         """Return item representation of requested entity
@@ -275,7 +286,7 @@
             # full cache is requested
             return cache
 
-        if name in ('status', C.PRESENCE_STATUSES, C.PRESENCE_PRIORITY, C.PRESENCE_SHOW):
+        if name in ("status", C.PRESENCE_STATUSES, C.PRESENCE_PRIORITY, C.PRESENCE_SHOW):
             # these data are related to the resource
             if not entity.resource:
                 main_resource = cache[C.CONTACT_MAIN_RESOURCE]
@@ -287,9 +298,9 @@
             else:
                 cache = cache[C.CONTACT_RESOURCES].setdefault(entity.resource, {})
 
-            if name == 'status':  # XXX: we get the first status for 'status' key
+            if name == "status":  # XXX: we get the first status for 'status' key
                 # TODO: manage main language for statuses
-                return cache[C.PRESENCE_STATUSES].get(C.PRESENCE_STATUSES_DEFAULT, '')
+                return cache[C.PRESENCE_STATUSES].get(C.PRESENCE_STATUSES_DEFAULT, "")
 
         elif entity.resource:
             try:
@@ -332,7 +343,7 @@
         @param name: name of the data (can't be "jids")
         @param value: value to set
         """
-        assert name is not 'jids'
+        assert name is not "jids"
         self._groups[group][name] = value
 
     def getGroupData(self, group, name=None):
@@ -388,8 +399,10 @@
         for entity in self._specials:
             if bare and entity.resource:
                 continue
-            if (special_type is not None
-                and self.getCache(entity, C.CONTACT_SPECIAL) != special_type):
+            if (
+                special_type is not None
+                and self.getCache(entity, C.CONTACT_SPECIAL) != special_type
+            ):
                 continue
             yield entity
 
@@ -440,9 +453,14 @@
         if in_roster:
             self._roster.add(entity_bare)
 
-        cache = self._cache.setdefault(entity_bare, {C.CONTACT_RESOURCES: {},
-                                                     C.CONTACT_MAIN_RESOURCE: None,
-                                                     C.CONTACT_SELECTED: set()})
+        cache = self._cache.setdefault(
+            entity_bare,
+            {
+                C.CONTACT_RESOURCES: {},
+                C.CONTACT_MAIN_RESOURCE: None,
+                C.CONTACT_SELECTED: set(),
+            },
+        )
 
         # we don't want forbidden data in attributes
         assert not C.CONTACT_DATA_FORBIDDEN.intersection(attributes)
@@ -454,13 +472,15 @@
             if C.CONTACT_GROUPS in cache:
                 # XXX: don't use set(cache[C.CONTACT_GROUPS]).difference(groups) because
                 #      it won't work in Pyjamas if None is in cache[C.CONTACT_GROUPS]
-                for group in [group for group in cache[C.CONTACT_GROUPS]
-                              if group not in groups]:
-                    self._groups[group]['jids'].remove(entity_bare)
+                for group in [
+                    group for group in cache[C.CONTACT_GROUPS] if group not in groups
+                ]:
+                    self._groups[group]["jids"].remove(entity_bare)
             cache[C.CONTACT_GROUPS] = groups
             for group in groups:
-                self._groups.setdefault(group, {}).setdefault('jids', set()).add(
-                    entity_bare)
+                self._groups.setdefault(group, {}).setdefault("jids", set()).add(
+                    entity_bare
+                )
 
         # special entities management
         if C.CONTACT_SPECIAL in attributes:
@@ -473,8 +493,11 @@
 
         # now the attributes we keep in cache
         # XXX: if entity is a full jid, we store the value for the resource only
-        cache_attr = (cache[C.CONTACT_RESOURCES].setdefault(entity.resource, {})
-                      if entity.resource else cache)
+        cache_attr = (
+            cache[C.CONTACT_RESOURCES].setdefault(entity.resource, {})
+            if entity.resource
+            else cache
+        )
         for attribute, value in attributes.iteritems():
             if value is None:
                 # XXX: pyjamas hack: we need to use pop instead of del
@@ -483,7 +506,9 @@
                 except KeyError:
                     pass
             else:
-                if attribute == 'nick' and self.isSpecial(entity, C.CONTACT_SPECIAL_GROUP):
+                if attribute == "nick" and self.isSpecial(
+                    entity, C.CONTACT_SPECIAL_GROUP
+                ):
                     # we don't want to keep nick for MUC rooms
                     # FIXME: this is here as plugin XEP-0054 can link resource's nick
                     #        with bare jid which in the case of MUC
@@ -518,12 +543,15 @@
             selected = self._selected
         else:
             selected = {selected.bare for selected in self._selected}
-        return ((show is not None and show != C.PRESENCE_UNAVAILABLE)
-                or self.show_disconnected
-                or entity in selected
-                or (self.show_entities_with_notifs
-                and next(self.host.getNotifs(entity.bare, profile=self.profile), None))
-                )
+        return (
+            (show is not None and show != C.PRESENCE_UNAVAILABLE)
+            or self.show_disconnected
+            or entity in selected
+            or (
+                self.show_entities_with_notifs
+                and next(self.host.getNotifs(entity.bare, profile=self.profile), None)
+            )
+        )
 
     def anyEntityVisible(self, entities, check_resources=False):
         """Tell if in a list of entities, at least one should be shown
@@ -564,8 +592,8 @@
             pass
         del self._cache[entity_bare]
         for group in groups:
-            self._groups[group]['jids'].remove(entity_bare)
-            if not self._groups[group]['jids']:
+            self._groups[group]["jids"].remove(entity_bare)
+            if not self._groups[group]["jids"]:
                 # FIXME: we use pop because of pyjamas:
                 #        http://wiki.goffi.org/wiki/Issues_with_Pyjamas/en
                 self._groups.pop(group)
@@ -599,14 +627,20 @@
                 try:
                     del cache[C.CONTACT_RESOURCES][entity.resource]
                 except KeyError:
-                    log.error(u"Presence unavailable received "
-                              u"for an unknown resource [{}]".format(entity))
+                    log.error(
+                        u"Presence unavailable received "
+                        u"for an unknown resource [{}]".format(entity)
+                    )
                 if not cache[C.CONTACT_RESOURCES]:
                     cache[C.CONTACT_MAIN_RESOURCE] = None
         else:
             if not entity.resource:
-                log.warning(_(u"received presence from entity "
-                              u"without resource: {}".format(entity)))
+                log.warning(
+                    _(
+                        u"received presence from entity "
+                        u"without resource: {}".format(entity)
+                    )
+                )
             resources_data = cache[C.CONTACT_RESOURCES]
             resource_data = resources_data.setdefault(entity.resource, {})
             resource_data[C.PRESENCE_SHOW] = show
@@ -616,9 +650,12 @@
             if entity.bare not in self._specials:
                 # we may have resources with no priority
                 # (when a cached value is added for a not connected resource)
-                priority_resource = max(resources_data,
-                                        key=lambda res: resources_data[res].get(
-                                            C.PRESENCE_PRIORITY, -2**32))
+                priority_resource = max(
+                    resources_data,
+                    key=lambda res: resources_data[res].get(
+                        C.PRESENCE_PRIORITY, -2 ** 32
+                    ),
+                )
                 cache[C.CONTACT_MAIN_RESOURCE] = priority_resource
         if self.entityVisible(entity.bare):
             update_type = C.UPDATE_MODIFY if was_visible else C.UPDATE_ADD
@@ -634,7 +671,7 @@
         @param profile: %(doc_profile)s
         """
         assert profile == self.profile
-        self.setCache(entity, 'nick', new_nick)
+        self.setCache(entity, "nick", new_nick)
 
     def onNotification(self, entity, notif, profile):
         """Update entity with notification
@@ -723,18 +760,18 @@
 
 
 class QuickContactListHandler(object):
-
     def __init__(self, host):
         super(QuickContactListHandler, self).__init__()
         self.host = host
         global handler
         if handler is not None:
-            raise exceptions.InternalError(u"QuickContactListHandler must be "
-                                           u"instanciated only once")
+            raise exceptions.InternalError(
+                u"QuickContactListHandler must be " u"instanciated only once"
+            )
         handler = self
-        self._clist = {} # key: profile, value: ProfileContactList
+        self._clist = {}  # key: profile, value: ProfileContactList
         self._widgets = set()
-        self._update_locked = False # se to True to ignore updates
+        self._update_locked = False  # se to True to ignore updates
 
     def __getitem__(self, profile):
         """Return ProfileContactList instance for the requested profile"""
@@ -845,16 +882,16 @@
         return self.items_sort(self.items)
 
     def items_sort(self, items):
-       """sort items
+        """sort items
 
        @param items(dict): items to sort (will be emptied !)
        @return (OrderedDict): sorted items
        """
-       ordered_items = OrderedDict()
-       bare_jids = sorted(items.keys())
-       for jid_ in bare_jids:
-           ordered_items[jid_] = items.pop(jid_)
-       return ordered_items
+        ordered_items = OrderedDict()
+        bare_jids = sorted(items.keys())
+        for jid_ in bare_jids:
+            ordered_items[jid_] = items.pop(jid_)
+        return ordered_items
 
     def register(self, widget):
         """Register a QuickContactList widget
@@ -940,8 +977,11 @@
 
         remaining = to_fill.difference(filled)
         if remaining != to_fill:
-            log.debug(u"Not re-filling already filled contact list(s) for {}".format(
-                u', '.join(to_fill.intersection(filled))))
+            log.debug(
+                u"Not re-filling already filled contact list(s) for {}".format(
+                    u", ".join(to_fill.intersection(filled))
+                )
+            )
         for profile in remaining:
             self._clist[profile]._fill()
 
@@ -973,8 +1013,11 @@
             if set to False, widget state can be inconsistent, be sure to know
             what youa re doing!
         """
-        log.debug(u"Contact lists updates are now {}".format(
-            u"LOCKED" if locked else u"UNLOCKED"))
+        log.debug(
+            u"Contact lists updates are now {}".format(
+                u"LOCKED" if locked else u"UNLOCKED"
+            )
+        )
         self._update_locked = locked
         if not locked and do_update:
             self.update()
@@ -987,10 +1030,11 @@
 
 class QuickContactList(QuickWidget):
     """This class manage the visual representation of contacts"""
-    SINGLE=False
-    PROFILES_MULTIPLE=True
+
+    SINGLE = False
+    PROFILES_MULTIPLE = True
     # Can be linked to no profile (e.g. at the early frontend start)
-    PROFILES_ALLOW_NONE=True
+    PROFILES_ALLOW_NONE = True
 
     def __init__(self, host, profiles):
         super(QuickContactList, self).__init__(host, None, profiles)
@@ -998,10 +1042,10 @@
         # options
         # for next values, None means use indivual value per profile
         # True or False mean override these values for all profiles
-        self.show_disconnected = None # TODO
-        self.show_empty_groups = None # TODO
-        self.show_resources = None # TODO
-        self.show_status = None # TODO
+        self.show_disconnected = None  # TODO
+        self.show_empty_groups = None  # TODO
+        self.show_resources = None  # TODO
+        self.show_status = None  # TODO
 
     def postInit(self):
         """Method to be called by frontend after widget is initialised"""
--- a/sat_frontends/quick_frontend/quick_contact_management.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/quick_frontend/quick_contact_management.py	Wed Jun 27 20:14:46 2018 +0200
@@ -19,12 +19,14 @@
 
 from sat.core.i18n import _
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
-from sat_frontends.tools.jid  import JID
+from sat_frontends.tools.jid import JID
 
 
 class QuickContactManagement(object):
     """This helper class manage the contacts and ease the use of nicknames and shortcuts"""
+
     ### FIXME: is SàT a better place for all this stuff ??? ###
 
     def __init__(self):
@@ -40,19 +42,19 @@
     def add(self, entity):
         """Add contact to the list, update resources"""
         if not self.__contactlist.has_key(entity.bare):
-            self.__contactlist[entity.bare] = {'resources':[]}
+            self.__contactlist[entity.bare] = {"resources": []}
         if not entity.resource:
             return
-        if entity.resource in self.__contactlist[entity.bare]['resources']:
-            self.__contactlist[entity.bare]['resources'].remove(entity.resource)
-        self.__contactlist[entity.bare]['resources'].append(entity.resource)
+        if entity.resource in self.__contactlist[entity.bare]["resources"]:
+            self.__contactlist[entity.bare]["resources"].remove(entity.resource)
+        self.__contactlist[entity.bare]["resources"].append(entity.resource)
 
     def getContFromGroup(self, group):
         """Return all contacts which are in given group"""
         result = []
         for contact in self.__contactlist:
-            if self.__contactlist[contact].has_key('groups'):
-                if group in self.__contactlist[contact]['groups']:
+            if self.__contactlist[contact].has_key("groups"):
+                if group in self.__contactlist[contact]["groups"]:
                     result.append(JID(contact))
         return result
 
@@ -62,13 +64,13 @@
         @param name: name of the attribute
         @return: asked attribute"""
         if self.__contactlist.has_key(entity.bare):
-            if name == 'status':  #FIXME: for the moment, we only use the first status
-                if self.__contactlist[entity.bare]['statuses']:
-                    return self.__contactlist[entity.bare]['statuses'].values()[0]
+            if name == "status":  # FIXME: for the moment, we only use the first status
+                if self.__contactlist[entity.bare]["statuses"]:
+                    return self.__contactlist[entity.bare]["statuses"].values()[0]
             if self.__contactlist[entity.bare].has_key(name):
                 return self.__contactlist[entity.bare][name]
         else:
-            log.debug(_('Trying to get attribute for an unknown contact'))
+            log.debug(_("Trying to get attribute for an unknown contact"))
         return None
 
     def isConnected(self, entity):
@@ -79,12 +81,12 @@
         """remove resource. If no more resource is online or is no resource is specified, contact is deleted"""
         try:
             if entity.resource:
-                self.__contactlist[entity.bare]['resources'].remove(entity.resource)
-            if not entity.resource or not self.__contactlist[entity.bare]['resources']:
-                #no more resource available: the contact seems really disconnected
+                self.__contactlist[entity.bare]["resources"].remove(entity.resource)
+            if not entity.resource or not self.__contactlist[entity.bare]["resources"]:
+                # no more resource available: the contact seems really disconnected
                 del self.__contactlist[entity.bare]
         except KeyError:
-            log.error(_('INTERNAL ERROR: Key log.error'))
+            log.error(_("INTERNAL ERROR: Key log.error"))
             raise
 
     def update(self, entity, key, value):
@@ -96,8 +98,7 @@
         if self.__contactlist.has_key(entity.bare):
             self.__contactlist[entity.bare][key] = value
         else:
-            log.debug (_('Trying to update an unknown contact: %s') % entity.bare)
+            log.debug(_("Trying to update an unknown contact: %s") % entity.bare)
 
     def get_full(self, entity):
-        return entity.bare+'/'+self.__contactlist[entity.bare]['resources'][-1]
-
+        return entity.bare + "/" + self.__contactlist[entity.bare]["resources"][-1]
--- a/sat_frontends/quick_frontend/quick_game_tarot.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/quick_frontend/quick_game_tarot.py	Wed Jun 27 20:14:46 2018 +0200
@@ -18,14 +18,14 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from sat_frontends.tools.jid import JID
 
 
 class QuickTarotGame(object):
-
     def __init__(self, parent, referee, players):
-        self._autoplay = None  #XXX: use 0 to activate fake play, None else
+        self._autoplay = None  # XXX: use 0 to activate fake play, None else
         self.parent = parent
         self.referee = referee
         self.players = players
@@ -42,8 +42,8 @@
         idx = (idx + 1) % len(self.players)
         self.left_nick = unicode(self.players[idx])
         self.bottom_nick = unicode(self.player_nick)
-        self.selected = []  #Card choosed by the player (e.g. during ecart)
-        self.hand_size = 13  #number of cards in a hand
+        self.selected = []  # Card choosed by the player (e.g. during ecart)
+        self.hand_size = 13  # number of cards in a hand
         self.hand = []
         self.to_show = []
         self.state = None
@@ -59,25 +59,27 @@
 
     def getPlayerLocation(self, nick):
         """return player location (top,bottom,left or right)"""
-        for location in ['top','left','bottom','right']:
-            if getattr(self,'%s_nick' % location) == nick:
+        for location in ["top", "left", "bottom", "right"]:
+            if getattr(self, "%s_nick" % location) == nick:
                 return location
-        assert(False)
+        assert False
 
     def loadCards(self):
         """Load all the cards in memory
         @param dir: directory where the PNG files are"""
-        self.cards={}
-        self.deck=[]
-        self.cards["atout"]={} #As Tarot is a french game, it's more handy & logical to keep french names
-        self.cards["pique"]={} #spade
-        self.cards["coeur"]={} #heart
-        self.cards["carreau"]={} #diamond
-        self.cards["trefle"]={} #club
+        self.cards = {}
+        self.deck = []
+        self.cards[
+            "atout"
+        ] = {}  # As Tarot is a french game, it's more handy & logical to keep french names
+        self.cards["pique"] = {}  # spade
+        self.cards["coeur"] = {}  # heart
+        self.cards["carreau"] = {}  # diamond
+        self.cards["trefle"] = {}  # club
 
     def tarotGameNewHandler(self, hand):
         """Start a new game, with given hand"""
-        assert (len(self.hand) == 0)
+        assert len(self.hand) == 0
         for suit, value in hand:
             self.hand.append(self.cards[suit, value])
         self.hand.sort()
@@ -93,7 +95,7 @@
         self.to_show = []
         for suit, value in cards:
             self.to_show.append(self.cards[suit, value])
-        if game_stage == "chien" and data['attaquant'] == self.player_nick:
+        if game_stage == "chien" and data["attaquant"] == self.player_nick:
             self.state = "wait_for_ecart"
         else:
             self.state = "chien"
@@ -113,10 +115,12 @@
         if self._autoplay >= len(self.hand):
             self._autoplay = 0
         card = self.hand[self._autoplay]
-        self.parent.host.bridge.tarotGamePlayCards(self.player_nick, self.referee, [(card.suit, card.value)], self.parent.profile)
+        self.parent.host.bridge.tarotGamePlayCards(
+            self.player_nick, self.referee, [(card.suit, card.value)], self.parent.profile
+        )
         del self.hand[self._autoplay]
         self.state = "wait"
-        self._autoplay+=1
+        self._autoplay += 1
 
     def tarotGameScoreHandler(self, xml_data, winners, loosers):
         """Called at the end of a game
@@ -130,7 +134,7 @@
         if self.to_show:
             self.to_show = []
         pl_cards = []
-        if self.played[player] != None: #FIXME
+        if self.played[player] != None:  # FIXME
             for pl in self.played:
                 self.played[pl] = None
         for suit, value in cards:
@@ -148,11 +152,10 @@
         elif phase == "ecart":
             self.state = "ecart"
         else:
-            log.error ('INTERNAL ERROR: unmanaged game phase')
+            log.error("INTERNAL ERROR: unmanaged game phase")
 
         for suit, value in played_cards:
             self.hand.append(self.cards[suit, value])
 
         self.hand.sort()
         self.__fakePlay()
-
--- a/sat_frontends/quick_frontend/quick_games.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/quick_frontend/quick_games.py	Wed Jun 27 20:14:46 2018 +0200
@@ -18,6 +18,7 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 
 from sat.core.i18n import _
@@ -36,23 +37,27 @@
 
     @classmethod
     def registerSignals(cls, host):
-
         def make_handler(suffix, signal):
             def handler(*args):
                 if suffix in ("Started", "Players"):
                     return cls.startedHandler(host, suffix, *args)
                 return cls.genericHandler(host, signal, *args)
+
             return handler
 
         for suffix in cls._signal_suffixes:
             signal = cls._signal_prefix + suffix
-            host.registerSignal(signal, handler=make_handler(suffix, signal), iface="plugin")
+            host.registerSignal(
+                signal, handler=make_handler(suffix, signal), iface="plugin"
+            )
 
     @classmethod
     def startedHandler(cls, host, suffix, *args):
         room_jid, args, profile = jid.JID(args[0]), args[1:-1], args[-1]
         referee, players, args = args[0], args[1], args[2:]
-        chat_widget = host.widgets.getOrCreateWidget(quick_chat.QuickChat, room_jid, type_=C.CHAT_GROUP, profile=profile)
+        chat_widget = host.widgets.getOrCreateWidget(
+            quick_chat.QuickChat, room_jid, type_=C.CHAT_GROUP, profile=profile
+        )
 
         # update symbols
         if cls._game_name not in chat_widget.visible_states:
@@ -62,7 +67,11 @@
         contact_list = host.contact_lists[profile]
         for occupant in chat_widget.occupants:
             occupant_jid = jid.newResource(room_jid, occupant)
-            contact_list.setCache(occupant_jid, cls._game_name, symbols[index % len(symbols)] if occupant in players else None)
+            contact_list.setCache(
+                occupant_jid,
+                cls._game_name,
+                symbols[index % len(symbols)] if occupant in players else None,
+            )
             chat_widget.update(occupant_jid)
 
         if suffix == "Players" or chat_widget.nick not in players:
@@ -71,8 +80,12 @@
             return  # game panel is already there
         real_class = host.widgets.getRealClass(cls)
         if real_class == cls:
-            host.showDialog(_(u"A {game} activity between {players} has been started, but you couldn't take part because your client doesn't support it.").format(game=cls._game_name, players=', '.join(players)),
-                            _(u"{game} Game").format(game=cls._game_name))
+            host.showDialog(
+                _(
+                    u"A {game} activity between {players} has been started, but you couldn't take part because your client doesn't support it."
+                ).format(game=cls._game_name, players=", ".join(players)),
+                _(u"{game} Game").format(game=cls._game_name),
+            )
             return
         panel = real_class(chat_widget, referee, players, *args)
         chat_widget.games[cls._game_name] = panel
@@ -86,7 +99,10 @@
             try:
                 game_panel = chat_widget.games[cls._game_name]
             except KeyError:
-                log.error("TODO: better game synchronisation - received signal %s but no panel is found" % signal)
+                log.error(
+                    "TODO: better game synchronisation - received signal %s but no panel is found"
+                    % signal
+                )
                 return
             else:
                 getattr(game_panel, "%sHandler" % signal)(*args)
@@ -95,23 +111,43 @@
 class Tarot(RoomGame):
     _game_name = "Tarot"
     _signal_prefix = "tarotGame"
-    _signal_suffixes = ("Started", "Players", "New", "ChooseContrat",
-                        "ShowCards", "YourTurn", "Score", "CardsPlayed",
-                        "InvalidCards",
-                        )
+    _signal_suffixes = (
+        "Started",
+        "Players",
+        "New",
+        "ChooseContrat",
+        "ShowCards",
+        "YourTurn",
+        "Score",
+        "CardsPlayed",
+        "InvalidCards",
+    )
 
 
 class Quiz(RoomGame):
     _game_name = "Quiz"
     _signal_prefix = "quizGame"
-    _signal_suffixes = ("Started", "New", "Question", "PlayerBuzzed",
-                        "PlayerSays", "AnswerResult", "TimerExpired",
-                        "TimerRestarted",
-                        )
+    _signal_suffixes = (
+        "Started",
+        "New",
+        "Question",
+        "PlayerBuzzed",
+        "PlayerSays",
+        "AnswerResult",
+        "TimerExpired",
+        "TimerRestarted",
+    )
 
 
 class Radiocol(RoomGame):
     _game_name = "Radiocol"
     _signal_prefix = "radiocol"
-    _signal_suffixes = ("Started", "Players", "SongRejected", "Preload",
-                        "Play", "NoUpload", "UploadOk")
+    _signal_suffixes = (
+        "Started",
+        "Players",
+        "SongRejected",
+        "Preload",
+        "Play",
+        "NoUpload",
+        "UploadOk",
+    )
--- a/sat_frontends/quick_frontend/quick_list_manager.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/quick_frontend/quick_list_manager.py	Wed Jun 27 20:14:46 2018 +0200
@@ -25,10 +25,14 @@
         """
 
         @param items (list): the suggested list of non tagged items
-        """ 
+        """
         self.tagged = []
-        self.original = items[:] if items else []  # XXX: copy the list! It will be modified
-        self.untagged = items[:] if items else []  # XXX: copy the list! It will be modified
+        self.original = (
+            items[:] if items else []
+        )  # XXX: copy the list! It will be modified
+        self.untagged = (
+            items[:] if items else []
+        )  # XXX: copy the list! It will be modified
         self.untagged.sort()
 
     @property
--- a/sat_frontends/quick_frontend/quick_menus.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/quick_frontend/quick_menus.py	Wed Jun 27 20:14:46 2018 +0200
@@ -19,12 +19,16 @@
 
 try:
     # FIXME: to be removed when an acceptable solution is here
-    unicode('')  # XXX: unicode doesn't exist in pyjamas
-except (TypeError, AttributeError):  # Error raised is not the same depending on pyjsbuild options
+    unicode("")  # XXX: unicode doesn't exist in pyjamas
+except (
+    TypeError,
+    AttributeError,
+):  # Error raised is not the same depending on pyjsbuild options
     unicode = str
 
 from sat.core.log import getLogger
 from sat.core.i18n import _, languageSwitch
+
 log = getLogger(__name__)
 from sat_frontends.quick_frontend.constants import Const as C
 from collections import OrderedDict
@@ -32,8 +36,9 @@
 
 ## items ##
 
+
 class MenuBase(object):
-    ACTIVE=True
+    ACTIVE = True
 
     def __init__(self, name, extra=None):
         """
@@ -61,7 +66,8 @@
 
 class MenuItem(MenuBase):
     """A callable item in the menu"""
-    CALLABLE=False
+
+    CALLABLE = False
 
     def __init__(self, name, name_i18n, extra=None, type_=None):
         """
@@ -83,7 +89,7 @@
 
         @param caller: Menu caller
         """
-        assert self.type is not None # if data collector are used, type must be set
+        assert self.type is not None  # if data collector are used, type must be set
         data_collector = QuickMenusManager.getDataCollector(self.type)
 
         if data_collector is None:
@@ -101,7 +107,6 @@
                 data[data_key] = unicode(getattr(caller, caller_attr))
             return data
 
-
     def call(self, caller, profile=C.PROF_KEY_NONE):
         """Execute the menu item
 
@@ -113,7 +118,8 @@
 
 class MenuItemDistant(MenuItem):
     """A MenuItem with a distant callback"""
-    CALLABLE=True
+
+    CALLABLE = True
 
     def __init__(self, host, type_, name, name_i18n, id_, extra=None):
         """
@@ -129,14 +135,15 @@
         self.id = id_
 
     def call(self, caller, profile=C.PROF_KEY_NONE):
-        data =  self.collectData(caller)
+        data = self.collectData(caller)
         log.debug("data collected: %s" % data)
         self.host.launchAction(self.id, data, profile=profile)
 
 
 class MenuItemLocal(MenuItem):
     """A MenuItem with a local callback"""
-    CALLABLE=True
+
+    CALLABLE = True
 
     def __init__(self, type_, name, name_i18n, callback, extra=None):
         """
@@ -162,12 +169,14 @@
 
 class MenuHook(MenuItemLocal):
     """A MenuItem which replace an expected item from backend"""
+
     pass
 
 
 class MenuPlaceHolder(MenuItem):
     """A non existant menu which is used to keep a position"""
-    ACTIVE=False
+
+    ACTIVE = False
 
     def __init__(self, name):
         MenuItem.__init__(self, name, name)
@@ -175,10 +184,11 @@
 
 class MenuSeparator(MenuItem):
     """A separation between items/categories"""
-    SEP_IDX=0
+
+    SEP_IDX = 0
 
     def __init__(self):
-        MenuSeparator.SEP_IDX +=1
+        MenuSeparator.SEP_IDX += 1
         name = u"___separator_{}".format(MenuSeparator.SEP_IDX)
         MenuItem.__init__(self, name, name)
 
@@ -187,7 +197,6 @@
 
 
 class MenuContainer(MenuBase):
-
     def __init__(self, name, extra=None):
         MenuBase.__init__(self, name, extra)
         self._items = OrderedDict()
@@ -208,7 +217,10 @@
             raise KeyError(item)
 
     def getOrCreate(self, item):
-        log.debug(u"MenuContainer getOrCreate: item=%s name=%s\nlist=%s" % (item, item.canonical, self._items.keys()))
+        log.debug(
+            u"MenuContainer getOrCreate: item=%s name=%s\nlist=%s"
+            % (item, item.canonical, self._items.keys())
+        )
         try:
             return self[item]
         except KeyError:
@@ -255,6 +267,7 @@
 
 class MenuType(MenuContainer):
     """A type which can hold other menus or categories"""
+
     pass
 
 
@@ -263,7 +276,10 @@
 
 class QuickMenusManager(object):
     """Manage all the menus"""
-    _data_collectors={C.MENU_GLOBAL: None} # No data is associated with C.MENU_GLOBAL items
+
+    _data_collectors = {
+        C.MENU_GLOBAL: None
+    }  # No data is associated with C.MENU_GLOBAL items
 
     def __init__(self, host, menus=None, language=None):
         """
@@ -358,15 +374,28 @@
                     menu_container.replace(item)
                 elif isinstance(container_item, MenuHook):
                     # MenuHook must not be replaced
-                    log.debug(u"ignoring menu at path [{}] because a hook is already in place".format(path))
+                    log.debug(
+                        u"ignoring menu at path [{}] because a hook is already in place".format(
+                            path
+                        )
+                    )
                 else:
                     log.error(u"Conflicting menus at path [{}]".format(path))
         else:
             log.debug(u"Adding menu [{type_}] {path}".format(type_=type_, path=path))
             menu_container.append(item)
-            self.host.callListeners('menu', type_, path, path_i18n, item)
+            self.host.callListeners("menu", type_, path, path_i18n, item)
 
-    def addMenu(self, type_, path, path_i18n=None, extra=None, top_extra=None, id_=None, callback=None):
+    def addMenu(
+        self,
+        type_,
+        path,
+        path_i18n=None,
+        extra=None,
+        top_extra=None,
+        id_=None,
+        callback=None,
+    ):
         """Add a menu item
 
         @param type_(unicode): same as in [sat.core.sat_main.SAT.importMenu]
@@ -379,11 +408,15 @@
         """
         if path_i18n is None:
             path_i18n = self._getPathI18n(path)
-        assert bool(id_) ^ bool(callback) # we must have id_ xor callback defined
+        assert bool(id_) ^ bool(callback)  # we must have id_ xor callback defined
         if id_:
-            menu_item = MenuItemDistant(self.host, type_, path[-1], path_i18n[-1], id_=id_, extra=extra)
+            menu_item = MenuItemDistant(
+                self.host, type_, path[-1], path_i18n[-1], id_=id_, extra=extra
+            )
         else:
-            menu_item = MenuItemLocal(type_, path[-1], path_i18n[-1], callback=callback, extra=extra)
+            menu_item = MenuItemLocal(
+                type_, path[-1], path_i18n[-1], callback=callback, extra=extra
+            )
         self.addMenuItem(type_, path[:-1], menu_item, path_i18n[:-1], top_extra)
 
     def addMenus(self, menus, top_extra=None):
@@ -401,11 +434,17 @@
         # TODO: manage icons
         for id_, type_, path, path_i18n, extra in menus:
             if callable(id_):
-                self.addMenu(type_, path, path_i18n, callback=id_, extra=extra, top_extra=top_extra)
+                self.addMenu(
+                    type_, path, path_i18n, callback=id_, extra=extra, top_extra=top_extra
+                )
             else:
-                self.addMenu(type_, path, path_i18n, id_=id_, extra=extra, top_extra=top_extra)
+                self.addMenu(
+                    type_, path, path_i18n, id_=id_, extra=extra, top_extra=top_extra
+                )
 
-    def addMenuHook(self, type_, path, path_i18n=None, extra=None, top_extra=None, callback=None):
+    def addMenuHook(
+        self, type_, path, path_i18n=None, extra=None, top_extra=None, callback=None
+    ):
         """Helper method to add a menu hook
 
         Menu hooks are local menus which override menu given by backend
@@ -418,7 +457,9 @@
         """
         if path_i18n is None:
             path_i18n = self._getPathI18n(path)
-        menu_item = MenuHook(type_, path[-1], path_i18n[-1], callback=callback, extra=extra)
+        menu_item = MenuHook(
+            type_, path[-1], path_i18n[-1], callback=callback, extra=extra
+        )
         self.addMenuItem(type_, path[:-1], menu_item, path_i18n[:-1], top_extra)
         log.info(u"Menu hook set on {path} ({type_})".format(path=path, type_=type_))
 
@@ -434,7 +475,9 @@
         """
         if path_i18n is None:
             path_i18n = self._getPathI18n(path)
-        last_container = self._createCategories(type_, path, path_i18n, top_extra=top_extra)
+        last_container = self._createCategories(
+            type_, path, path_i18n, top_extra=top_extra
+        )
         last_container.setExtra(extra)
         return last_container
 
--- a/sat_frontends/quick_frontend/quick_profile_manager.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/quick_frontend/quick_profile_manager.py	Wed Jun 27 20:14:46 2018 +0200
@@ -19,6 +19,7 @@
 
 from sat.core.i18n import _
 from sat.core import log as logging
+
 log = logging.getLogger(__name__)
 from sat_frontends.primitivus.constants import Const as C
 
@@ -85,10 +86,9 @@
             log.warning("No profile given to autoconnect")
             return
         self._autoconnect = True
-        self._autoconnect_profiles=[]
+        self._autoconnect_profiles = []
         self._do_autoconnect(profile_keys)
 
-
     def _do_autoconnect(self, profile_keys):
         """Connect automatically given profiles
 
@@ -98,36 +98,39 @@
 
         def authenticate_cb(data, cb_id, profile):
 
-            if C.bool(data.pop('validated', C.BOOL_FALSE)):
+            if C.bool(data.pop("validated", C.BOOL_FALSE)):
                 self._autoconnect_profiles.append(profile)
                 if len(self._autoconnect_profiles) == len(profile_keys):
                     # all the profiles have been validated
                     self.host.plug_profiles(self._autoconnect_profiles)
             else:
                 # a profile is not validated, we go to manual mode
-                self._autoconnect=False
+                self._autoconnect = False
             self.host.actionManager(data, callback=authenticate_cb, profile=profile)
 
         def getProfileNameCb(profile):
             if not profile:
                 # FIXME: this method is not handling manual mode correclty anymore
                 #        must be thought to be handled asynchronously
-                self._autoconnect = False # manual mode
+                self._autoconnect = False  # manual mode
                 msg = _("Trying to plug an unknown profile key ({})".format(profile_key))
                 log.warning(msg)
-                self.host.showDialog(_("Profile plugging in error"), msg, 'error')
+                self.host.showDialog(_("Profile plugging in error"), msg, "error")
             else:
-                self.host.launchAction(C.AUTHENTICATE_PROFILE_ID, callback=authenticate_cb, profile=profile)
+                self.host.launchAction(
+                    C.AUTHENTICATE_PROFILE_ID, callback=authenticate_cb, profile=profile
+                )
 
         def getProfileNameEb(failure):
             log.error(u"Can't retrieve profile name: {}".format(failure))
 
         for profile_key in profile_keys:
-            self.host.bridge.profileNameGet(profile_key, callback=getProfileNameCb, errback=getProfileNameEb)
-
+            self.host.bridge.profileNameGet(
+                profile_key, callback=getProfileNameCb, errback=getProfileNameEb
+            )
 
     def getParamError(self, dummy):
-        self.host.showDialog(_(u"Error"), _("Can't get profile parameter"), 'error')
+        self.host.showDialog(_(u"Error"), _("Can't get profile parameter"), "error")
 
     ## Helping methods ##
 
@@ -142,7 +145,9 @@
         elif reason == "CancelError":
             message = _("Profile creation cancelled by backend")
         elif reason == "ValueError":
-            message = _("You profile name is not valid") # TODO: print a more informative message (empty name, name starting with '@')
+            message = _(
+                "You profile name is not valid"
+            )  # TODO: print a more informative message (empty name, name starting with '@')
         else:
             message = _("Can't create profile ({})").format(reason)
         return message
@@ -150,7 +155,9 @@
     def _deleteProfile(self):
         """Delete the currently selected profile"""
         if self.current.profile:
-            self.host.bridge.asyncDeleteProfile(self.current.profile, callback=self.refillProfiles)
+            self.host.bridge.asyncDeleteProfile(
+                self.current.profile, callback=self.refillProfiles
+            )
             self.resetFields()
 
     ## workflow methods (events occuring during the profiles selection) ##
@@ -160,12 +167,20 @@
     def _onConnectProfiles(self):
         """Connect the profiles and start the main widget"""
         if self._autoconnect:
-            self.host.showDialog(_('Internal error'), _("You can't connect manually and automatically at the same time"), 'error')
+            self.host.showDialog(
+                _("Internal error"),
+                _("You can't connect manually and automatically at the same time"),
+                "error",
+            )
             return
         self.updateConnectionParams()
         profiles = self.getProfiles()
         if not profiles:
-            self.host.showDialog(_('No profile selected'), _('You need to create and select at least one profile before connecting'), 'error')
+            self.host.showDialog(
+                _("No profile selected"),
+                _("You need to create and select at least one profile before connecting"),
+                "error",
+            )
         else:
             # All profiles in the list are already validated, so we can plug them directly
             self.host.plug_profiles(profiles)
@@ -175,8 +190,20 @@
 
         @param profile: %(doc_profile)s
         """
-        self.host.bridge.asyncGetParamA("JabberID", "Connection", profile_key=profile, callback=self.setJID, errback=self.getParamError)
-        self.host.bridge.asyncGetParamA("Password", "Connection", profile_key=profile, callback=self.setPassword, errback=self.getParamError)
+        self.host.bridge.asyncGetParamA(
+            "JabberID",
+            "Connection",
+            profile_key=profile,
+            callback=self.setJID,
+            errback=self.getParamError,
+        )
+        self.host.bridge.asyncGetParamA(
+            "Password",
+            "Connection",
+            profile_key=profile,
+            callback=self.setPassword,
+            errback=self.getParamError,
+        )
 
     def updateConnectionParams(self):
         """Check if connection parameters have changed, and update them if so"""
@@ -185,18 +212,24 @@
             password = self.getPassword()
             if login != self.current.login and self.current.login is not None:
                 self.current.login = login
-                self.host.bridge.setParam("JabberID", login, "Connection", profile_key=self.current.profile)
+                self.host.bridge.setParam(
+                    "JabberID", login, "Connection", profile_key=self.current.profile
+                )
                 log.info(u"login updated for profile [{}]".format(self.current.profile))
             if password != self.current.password and self.current.password is not None:
                 self.current.password = password
-                self.host.bridge.setParam("Password", password, "Connection", profile_key=self.current.profile)
-                log.info(u"password updated for profile [{}]".format(self.current.profile))
+                self.host.bridge.setParam(
+                    "Password", password, "Connection", profile_key=self.current.profile
+                )
+                log.info(
+                    u"password updated for profile [{}]".format(self.current.profile)
+                )
 
     ## graphic updates (should probably be overriden in frontends) ##
 
     def resetFields(self):
         """Set profile to None, and reset fields"""
-        self.current.profile=None
+        self.current.profile = None
         self.setJID("")
         self.setPassword("")
 
@@ -222,7 +255,6 @@
         """Update the list of profiles"""
         raise NotImplementedError
 
-
     def getJID(self):
         """Get current jid
 
--- a/sat_frontends/quick_frontend/quick_utils.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/quick_frontend/quick_utils.py	Wed Jun 27 20:14:46 2018 +0200
@@ -21,6 +21,7 @@
 from os.path import exists, splitext
 from optparse import OptionParser
 
+
 def getNewPath(path):
     """ Check if path exists, and find a non existant path if needed """
     idx = 2
@@ -31,21 +32,23 @@
         new_path = "%s_%d%s" % (root, idx, ext)
         if not exists(new_path):
             return new_path
-        idx+=1
+        idx += 1
+
 
 def check_options():
     """Check command line options"""
-    usage = _("""
+    usage = _(
+        """
     %prog [options]
 
     %prog --help for options list
-    """)
-    parser = OptionParser(usage=usage) # TODO: use argparse
+    """
+    )
+    parser = OptionParser(usage=usage)  # TODO: use argparse
 
     parser.add_option("-p", "--profile", help=_("Select the profile to use"))
 
     (options, args) = parser.parse_args()
     if options.profile:
-        options.profile = options.profile.decode('utf-8')
+        options.profile = options.profile.decode("utf-8")
     return options
-
--- a/sat_frontends/quick_frontend/quick_widgets.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/quick_frontend/quick_widgets.py	Wed Jun 27 20:14:46 2018 +0200
@@ -18,19 +18,23 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from sat.core import exceptions
 from sat_frontends.quick_frontend.constants import Const as C
 
 
-NEW_INSTANCE_SUFF = '_new_instance_'
+NEW_INSTANCE_SUFF = "_new_instance_"
 classes_map = {}
 
 
 try:
     # FIXME: to be removed when an acceptable solution is here
-    unicode('') # XXX: unicode doesn't exist in pyjamas
-except (TypeError, AttributeError): # Error raised is not the same depending on pyjsbuild options
+    unicode("")  # XXX: unicode doesn't exist in pyjamas
+except (
+    TypeError,
+    AttributeError,
+):  # Error raised is not the same depending on pyjsbuild options
     unicode = str
 
 
@@ -77,7 +81,9 @@
         except KeyError:
             cls = class_
         if cls is None:
-            raise exceptions.InternalError("There is not class registered for {}".format(class_))
+            raise exceptions.InternalError(
+                "There is not class registered for {}".format(class_)
+            )
         return cls
 
     def getRootHash(self, hash_):
@@ -109,7 +115,10 @@
                 filter_hash = None
             for w_hash, w in widgets_map.iteritems():
                 if profiles is None or w.profiles.intersection(profiles):
-                    if filter_hash is not None and self.getRootHash(w_hash) != filter_hash:
+                    if (
+                        filter_hash is not None
+                        and self.getRootHash(w_hash) != filter_hash
+                    ):
                         continue
                     yield w
 
@@ -161,39 +170,45 @@
         cls = self.getRealClass(class_)
 
         ## arguments management ##
-        _args = [self.host, target] + list(args) or [] # FIXME: check if it's really necessary to use optional args
+        _args = [self.host, target] + list(
+            args
+        ) or []  # FIXME: check if it's really necessary to use optional args
         _kwargs = kwargs or {}
-        if 'profiles' in _kwargs and 'profile' in _kwargs:
-            raise ValueError("You can't have 'profile' and 'profiles' keys at the same time")
+        if "profiles" in _kwargs and "profile" in _kwargs:
+            raise ValueError(
+                "You can't have 'profile' and 'profiles' keys at the same time"
+            )
         try:
-            _kwargs['profiles'] = [_kwargs.pop('profile')]
+            _kwargs["profiles"] = [_kwargs.pop("profile")]
         except KeyError:
-            if not 'profiles' in _kwargs:
-                _kwargs['profiles'] = None
+            if not "profiles" in _kwargs:
+                _kwargs["profiles"] = None
 
-        #on_new_widget tell what to do for the new widget creation
+        # on_new_widget tell what to do for the new widget creation
         try:
-            on_new_widget = _kwargs.pop('on_new_widget')
+            on_new_widget = _kwargs.pop("on_new_widget")
         except KeyError:
             on_new_widget = C.WIDGET_NEW
 
-        #on_existing_widget tell what to do when the widget already exists
+        # on_existing_widget tell what to do when the widget already exists
         try:
-            on_existing_widget = _kwargs.pop('on_existing_widget')
+            on_existing_widget = _kwargs.pop("on_existing_widget")
         except KeyError:
             on_existing_widget = C.WIDGET_KEEP
 
         ## we get the hash ##
         try:
-            hash_ = _kwargs.pop('force_hash')
+            hash_ = _kwargs.pop("force_hash")
         except KeyError:
-            hash_ = cls.getWidgetHash(target, _kwargs['profiles'])
+            hash_ = cls.getWidgetHash(target, _kwargs["profiles"])
 
         ## widget creation or retrieval ##
 
-        widgets_map = self._widgets.setdefault(cls.__name__, {}) # we sorts widgets by classes
+        widgets_map = self._widgets.setdefault(
+            cls.__name__, {}
+        )  # we sorts widgets by classes
         if not cls.SINGLE:
-            widget = None # if the class is not SINGLE, we always create a new widget
+            widget = None  # if the class is not SINGLE, we always create a new widget
         else:
             try:
                 widget = widgets_map[hash_]
@@ -225,16 +240,20 @@
                 #     we need to get rid of kwargs special options
                 new_kwargs = kwargs.copy()
                 try:
-                    new_kwargs.pop('force_hash')  # FIXME: we use pop instead of del here because pyjamas doesn't raise error on del
+                    new_kwargs.pop(
+                        "force_hash"
+                    )  # FIXME: we use pop instead of del here because pyjamas doesn't raise error on del
                 except KeyError:
                     pass
                 else:
-                    raise ValueError("force_hash option can't be used with on_existing_widget=RECREATE")
+                    raise ValueError(
+                        "force_hash option can't be used with on_existing_widget=RECREATE"
+                    )
 
-                new_kwargs['on_new_widget'] = on_new_widget
+                new_kwargs["on_new_widget"] = on_new_widget
 
                 # XXX: keep up-to-date if new special kwargs are added (i.e.: delete these keys here)
-                new_kwargs['on_existing_widget'] = C.WIDGET_RAISE
+                new_kwargs["on_existing_widget"] = C.WIDGET_RAISE
                 try:
                     recreateArgs = widget.recreateArgs
                 except AttributeError:
@@ -243,18 +262,28 @@
                     recreateArgs(args, new_kwargs)
                 hash_idx = 1
                 while True:
-                    new_kwargs['force_hash'] = "{}{}{}".format(hash_, NEW_INSTANCE_SUFF, hash_idx)
+                    new_kwargs["force_hash"] = "{}{}{}".format(
+                        hash_, NEW_INSTANCE_SUFF, hash_idx
+                    )
                     try:
-                        widget = self.getOrCreateWidget(class_, target, *args, **new_kwargs)
+                        widget = self.getOrCreateWidget(
+                            class_, target, *args, **new_kwargs
+                        )
                     except WidgetAlreadyExistsError:
                         hash_idx += 1
                     else:
-                        log.debug(u"Widget already exists, a new one has been recreated with hash {}".format(new_kwargs['force_hash']))
+                        log.debug(
+                            u"Widget already exists, a new one has been recreated with hash {}".format(
+                                new_kwargs["force_hash"]
+                            )
+                        )
                         break
             elif callable(on_existing_widget):
                 on_existing_widget(widget)
             else:
-                raise exceptions.InternalError("Unexpected on_existing_widget value ({})".format(on_existing_widget))
+                raise exceptions.InternalError(
+                    "Unexpected on_existing_widget value ({})".format(on_existing_widget)
+                )
 
         return widget
 
@@ -286,9 +315,10 @@
 
 class QuickWidget(object):
     """generic widget base"""
-    SINGLE=True # if True, there can be only one widget per target(s)
-    PROFILES_MULTIPLE=False # If True, this widget can handle several profiles at once
-    PROFILES_ALLOW_NONE=False # If True, this widget can be used without profile
+
+    SINGLE = True  # if True, there can be only one widget per target(s)
+    PROFILES_MULTIPLE = False  # If True, this widget can handle several profiles at once
+    PROFILES_ALLOW_NONE = False  # If True, this widget can be used without profile
 
     def __init__(self, host, target, profiles=None):
         """
@@ -317,7 +347,11 @@
 
     @property
     def profile(self):
-        assert len(self.profiles) == 1 and not self.PROFILES_MULTIPLE and not self.PROFILES_ALLOW_NONE
+        assert (
+            len(self.profiles) == 1
+            and not self.PROFILES_MULTIPLE
+            and not self.PROFILES_ALLOW_NONE
+        )
         return list(self.profiles)[0]
 
     def addTarget(self, target):
@@ -350,7 +384,7 @@
         @param profiles: profile(s) associated to target, see __init__ docstring
         @return: a hash (can correspond to one or many targets or profiles, depending of widget class)
         """
-        return unicode(target) # by defaut, there is one hash for one target
+        return unicode(target)  # by defaut, there is one hash for one target
 
     def onDelete(self, *args, **kwargs):
         """Called when a widget is being deleted
@@ -360,4 +394,3 @@
         """
         log.debug(u"widget {} deleted".format(self))
         return True
-
--- a/sat_frontends/tools/composition.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/tools/composition.py	Wed Jun 27 20:14:46 2018 +0200
@@ -20,71 +20,90 @@
 """
 
 # Map the messages recipient types to their properties.
-RECIPIENT_TYPES = {"To": {"desc": "Direct recipients", "optional": False},
-                   "Cc": {"desc": "Carbon copies", "optional": True},
-                   "Bcc": {"desc": "Blind carbon copies", "optional": True}}
+RECIPIENT_TYPES = {
+    "To": {"desc": "Direct recipients", "optional": False},
+    "Cc": {"desc": "Carbon copies", "optional": True},
+    "Bcc": {"desc": "Blind carbon copies", "optional": True},
+}
 
 # Rich text buttons icons and descriptions
 RICH_BUTTONS = {
     "bold": {"tip": "Bold", "icon": "media/icons/dokuwiki/toolbar/16/bold.png"},
     "italic": {"tip": "Italic", "icon": "media/icons/dokuwiki/toolbar/16/italic.png"},
-    "underline": {"tip": "Underline", "icon": "media/icons/dokuwiki/toolbar/16/underline.png"},
+    "underline": {
+        "tip": "Underline",
+        "icon": "media/icons/dokuwiki/toolbar/16/underline.png",
+    },
     "code": {"tip": "Code", "icon": "media/icons/dokuwiki/toolbar/16/mono.png"},
-    "strikethrough": {"tip": "Strikethrough", "icon": "media/icons/dokuwiki/toolbar/16/strike.png"},
+    "strikethrough": {
+        "tip": "Strikethrough",
+        "icon": "media/icons/dokuwiki/toolbar/16/strike.png",
+    },
     "heading": {"tip": "Heading", "icon": "media/icons/dokuwiki/toolbar/16/hequal.png"},
-    "numberedlist": {"tip": "Numbered List", "icon": "media/icons/dokuwiki/toolbar/16/ol.png"},
+    "numberedlist": {
+        "tip": "Numbered List",
+        "icon": "media/icons/dokuwiki/toolbar/16/ol.png",
+    },
     "list": {"tip": "List", "icon": "media/icons/dokuwiki/toolbar/16/ul.png"},
     "link": {"tip": "Link", "icon": "media/icons/dokuwiki/toolbar/16/linkextern.png"},
-    "horizontalrule": {"tip": "Horizontal rule", "icon": "media/icons/dokuwiki/toolbar/16/hr.png"},
-    "image":  {"tip": "Image", "icon": "media/icons/dokuwiki/toolbar/16/image.png"},
-    }
+    "horizontalrule": {
+        "tip": "Horizontal rule",
+        "icon": "media/icons/dokuwiki/toolbar/16/hr.png",
+    },
+    "image": {"tip": "Image", "icon": "media/icons/dokuwiki/toolbar/16/image.png"},
+}
 
 # Define here your rich text syntaxes, the key must match the ones used in button.
 # Tupples values must have 3 elements : prefix to the selection or cursor
 # position, sample text to write if the marker is not applied on a selection,
 # suffix to the selection or cursor position.
 # FIXME: must not be hard-coded like this
-RICH_SYNTAXES = {"markdown": {"bold": ("**", "bold", "**"),
-                              "italic": ("*", "italic", "*"),
-                              "code": ("`", "code", "`"),
-                              "heading": ("\n# ", "Heading 1", "\n## Heading 2\n"),
-                              "link": ("[desc](", "link", ")"),
-                              "list": ("\n* ", "item", "\n    + subitem\n"),
-                              "horizontalrule": ("\n***\n", "", ""),
-                              "image": ("![desc](", "path", ")"),
-                        },
-           "bbcode": {"bold": ("[b]", "bold", "[/b]"),
-                      "italic": ("[i]", "italic", "[/i]"),
-                      "underline": ("[u]", "underline", "[/u]"),
-                      "code": ("[code]", "code", "[/code]"),
-                      "strikethrough": ("[s]", "strikethrough", "[/s]"),
-                      "link": ("[url=", "link", "]desc[/url]"),
-                      "list": ("\n[list] [*]", "item 1", " [*]item 2 [/list]\n"),
-                      "image": ("[img alt=\"desc\]", "path", "[/img]"),
-                     },
-           "dokuwiki": {"bold": ("**", "bold", "**"),
-                        "italic": ("//", "italic", "//"),
-                        "underline": ("__", "underline", "__"),
-                        "code": ("<code>", "code", "</code>"),
-                        "strikethrough": ("<del>", "strikethrough", "</del>"),
-                        "heading": ("\n==== ", "Heading 1", " ====\n=== Heading 2 ===\n"),
-                        "link": ("[[", "link", "|desc]]"),
-                        "list": ("\n  * ", "item\n", "\n    * subitem\n"),
-                        "horizontalrule": ("\n----\n", "", ""),
-                        "image": ("{{", "path", " |desc}}"),
-                        },
-           "XHTML": {"bold": ("<b>", "bold", "</b>"),
-                     "italic": ("<i>", "italic", "</i>"),
-                     "underline": ("<u>", "underline", "</u>"),
-                     "code": ("<pre>", "code", "</pre>"),
-                     "strikethrough": ("<s>", "strikethrough", "</s>"),
-                     "heading": ("\n<h3>", "Heading 1", "</h3>\n<h4>Heading 2</h4>\n"),
-                     "link": ("<a href=\"", "link", "\">desc</a>"),
-                     "list": ("\n<ul><li>", "item 1", "</li><li>item 2</li></ul>\n"),
-                     "horizontalrule": ("\n<hr/>\n", "", ""),
-                     "image": ("<img src=\"", "path", "\" alt=\"desc\"/>"),
-                     }
-                 }
+RICH_SYNTAXES = {
+    "markdown": {
+        "bold": ("**", "bold", "**"),
+        "italic": ("*", "italic", "*"),
+        "code": ("`", "code", "`"),
+        "heading": ("\n# ", "Heading 1", "\n## Heading 2\n"),
+        "link": ("[desc](", "link", ")"),
+        "list": ("\n* ", "item", "\n    + subitem\n"),
+        "horizontalrule": ("\n***\n", "", ""),
+        "image": ("![desc](", "path", ")"),
+    },
+    "bbcode": {
+        "bold": ("[b]", "bold", "[/b]"),
+        "italic": ("[i]", "italic", "[/i]"),
+        "underline": ("[u]", "underline", "[/u]"),
+        "code": ("[code]", "code", "[/code]"),
+        "strikethrough": ("[s]", "strikethrough", "[/s]"),
+        "link": ("[url=", "link", "]desc[/url]"),
+        "list": ("\n[list] [*]", "item 1", " [*]item 2 [/list]\n"),
+        "image": ('[img alt="desc\]', "path", "[/img]"),
+    },
+    "dokuwiki": {
+        "bold": ("**", "bold", "**"),
+        "italic": ("//", "italic", "//"),
+        "underline": ("__", "underline", "__"),
+        "code": ("<code>", "code", "</code>"),
+        "strikethrough": ("<del>", "strikethrough", "</del>"),
+        "heading": ("\n==== ", "Heading 1", " ====\n=== Heading 2 ===\n"),
+        "link": ("[[", "link", "|desc]]"),
+        "list": ("\n  * ", "item\n", "\n    * subitem\n"),
+        "horizontalrule": ("\n----\n", "", ""),
+        "image": ("{{", "path", " |desc}}"),
+    },
+    "XHTML": {
+        "bold": ("<b>", "bold", "</b>"),
+        "italic": ("<i>", "italic", "</i>"),
+        "underline": ("<u>", "underline", "</u>"),
+        "code": ("<pre>", "code", "</pre>"),
+        "strikethrough": ("<s>", "strikethrough", "</s>"),
+        "heading": ("\n<h3>", "Heading 1", "</h3>\n<h4>Heading 2</h4>\n"),
+        "link": ('<a href="', "link", '">desc</a>'),
+        "list": ("\n<ul><li>", "item 1", "</li><li>item 2</li></ul>\n"),
+        "horizontalrule": ("\n<hr/>\n", "", ""),
+        "image": ('<img src="', "path", '" alt="desc"/>'),
+    },
+}
 
 # Define here the commands that are supported by the WYSIWYG edition.
 # Keys must be the same than the ones used in RICH_SYNTAXES["XHTML"].
@@ -93,21 +112,21 @@
 # - a tuple (cmd, prompt, arg) with cmd the name of the command,
 #   prompt the text to display for asking a user input and arg is the
 #   value to use directly without asking the user if prompt is empty.
-COMMANDS = {"bold": "bold",
-            "italic": "italic",
-            "underline": "underline",
-            "code": ("formatBlock", "", "pre"),
-            "strikethrough": "strikeThrough",
-            "heading": ("heading", "Please specify the heading level (h1, h2, h3...)", ""),
-            "link": ("createLink", "Please specify an URL", ""),
-            "list": "insertUnorderedList",
-            "horizontalrule": "insertHorizontalRule",
-            "image": ("insertImage", "Please specify an image path", ""),
-            }
+COMMANDS = {
+    "bold": "bold",
+    "italic": "italic",
+    "underline": "underline",
+    "code": ("formatBlock", "", "pre"),
+    "strikethrough": "strikeThrough",
+    "heading": ("heading", "Please specify the heading level (h1, h2, h3...)", ""),
+    "link": ("createLink", "Please specify an URL", ""),
+    "list": "insertUnorderedList",
+    "horizontalrule": "insertHorizontalRule",
+    "image": ("insertImage", "Please specify an image path", ""),
+}
 
 # These values should be equal to the ones in plugin_misc_text_syntaxes
 # FIXME: should the plugin import them from here to avoid duplicity? Importing
 # the plugin's values from here is not possible because Libervia would fail.
 PARAM_KEY_COMPOSITION = "Composition"
 PARAM_NAME_SYNTAX = "Syntax"
-
--- a/sat_frontends/tools/css_color.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/tools/css_color.py	Wed Jun 27 20:14:46 2018 +0200
@@ -18,6 +18,7 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 
 
@@ -167,7 +168,7 @@
     u"wheat": u"f5deb3",
     u"whitesmoke": u"f5f5f5",
     u"yellowgreen": u"9acd32",
-    u"rebeccapurple": u"663399"
+    u"rebeccapurple": u"663399",
 }
 DEFAULT = u"000000"
 
@@ -185,23 +186,23 @@
         If value can't be parsed, a warning message is logged, and DEFAULT is returned
     """
     raw_value = raw_value.strip().lower()
-    if raw_value.startswith(u'#'):
+    if raw_value.startswith(u"#"):
         # we have a hexadecimal value
         str_value = raw_value[1:]
-        if len(raw_value) in (3,4):
-            str_value = u''.join([2*v for v in str_value])
-    elif raw_value.startswith(u'rgb'):
-        left_p = raw_value.find(u'(')
-        right_p = raw_value.find(u')')
-        rgb_values = [v.strip() for v in raw_value[left_p+1:right_p].split(',')]
-        expected_len = 4 if raw_value.startswith(u'rgba') else 3
+        if len(raw_value) in (3, 4):
+            str_value = u"".join([2 * v for v in str_value])
+    elif raw_value.startswith(u"rgb"):
+        left_p = raw_value.find(u"(")
+        right_p = raw_value.find(u")")
+        rgb_values = [v.strip() for v in raw_value[left_p + 1 : right_p].split(",")]
+        expected_len = 4 if raw_value.startswith(u"rgba") else 3
         if len(rgb_values) != expected_len:
             log.warning(u"incorrect value: {}".format(raw_value))
             str_value = DEFAULT
         else:
             int_values = []
             for rgb_v in rgb_values:
-                p_idx = rgb_v.find(u'%')
+                p_idx = rgb_v.find(u"%")
                 if p_idx == -1:
                     # base 10 value
                     try:
@@ -222,8 +223,8 @@
                     except ValueError:
                         log.warning(u"invalid percent value: {}".format(rgb_v))
                         int_values.append(0)
-            str_value = u''.join([u"{:02x}".format(v) for v in int_values])
-    elif raw_value.startswith(u'hsl'):
+            str_value = u"".join([u"{:02x}".format(v) for v in int_values])
+    elif raw_value.startswith(u"hsl"):
         log.warning(u"hue-saturation-lightness not handled yet")  # TODO
         str_value = DEFAULT
     else:
@@ -236,4 +237,9 @@
     if as_string:
         return str_value
     else:
-        return tuple([int(str_value[i]+str_value[i+1], 16) for i in xrange(0, len(str_value), 2)])
+        return tuple(
+            [
+                int(str_value[i] + str_value[i + 1], 16)
+                for i in xrange(0, len(str_value), 2)
+            ]
+        )
--- a/sat_frontends/tools/games.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/tools/games.py	Wed Jun 27 20:14:46 2018 +0200
@@ -19,7 +19,13 @@
 
 """This library help manage general games (e.g. card games) and it is shared by the frontends"""
 
-SUITS_ORDER = ['pique', 'coeur', 'trefle', 'carreau', 'atout']  # I have switched the usual order 'trefle' and 'carreau' because card are more easy to see if suit colour change (black, red, black, red)
+SUITS_ORDER = [
+    "pique",
+    "coeur",
+    "trefle",
+    "carreau",
+    "atout",
+]  # I have switched the usual order 'trefle' and 'carreau' because card are more easy to see if suit colour change (black, red, black, red)
 VALUES_ORDER = [str(i) for i in xrange(1, 11)] + ["valet", "cavalier", "dame", "roi"]
 
 
@@ -58,12 +64,12 @@
             idx1 = SUITS_ORDER.index(self.suit)
             idx2 = SUITS_ORDER.index(other.suit)
             return idx1.__cmp__(idx2)
-        if self.suit == 'atout':
-            if self.value == other.value == 'excuse':
+        if self.suit == "atout":
+            if self.value == other.value == "excuse":
                 return 0
-            if self.value == 'excuse':
+            if self.value == "excuse":
                 return -1
-            if other.value == 'excuse':
+            if other.value == "excuse":
                 return 1
             return int(self.value).__cmp__(int(other.value))
         # at this point we have the same suit which is not 'atout'
--- a/sat_frontends/tools/host_listener.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/tools/host_listener.py	Wed Jun 27 20:14:46 2018 +0200
@@ -22,6 +22,7 @@
 
 listeners = []
 
+
 def addListener(cb):
     """Add a listener which will be called when host is ready
 
@@ -29,6 +30,7 @@
     """
     listeners.append(cb)
 
+
 def callListeners(host):
     """Must be called by frontend when host is ready.
 
--- a/sat_frontends/tools/jid.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/tools/jid.py	Wed Jun 27 20:14:46 2018 +0200
@@ -20,7 +20,7 @@
 
 # hack to use this module with pyjamas
 try:
-    unicode('')  # XXX: unicode doesn't exist in pyjamas
+    unicode("")  # XXX: unicode doesn't exist in pyjamas
 
     # normal version
     class BaseJID(unicode):
@@ -33,19 +33,23 @@
 
         def _parse(self):
             """Find node domain and resource"""
-            node_end = self.find('@')
+            node_end = self.find("@")
             if node_end < 0:
                 node_end = 0
-            domain_end = self.find('/')
+            domain_end = self.find("/")
             if domain_end == 0:
                 raise ValueError("a jid can't start with '/'")
             if domain_end == -1:
                 domain_end = len(self)
             self.node = self[:node_end] or None
-            self.domain = self[(node_end + 1) if node_end else 0:domain_end]
-            self.resource = self[domain_end + 1:] or None
+            self.domain = self[(node_end + 1) if node_end else 0 : domain_end]
+            self.resource = self[domain_end + 1 :] or None
+
 
-except (TypeError, AttributeError):  # Error raised is not the same depending on pyjsbuild options
+except (
+    TypeError,
+    AttributeError,
+):  # Error raised is not the same depending on pyjsbuild options
 
     # pyjamas version
     class BaseJID(object):
@@ -61,29 +65,33 @@
         def __eq__(self, other):
             if not isinstance(other, JID):
                 return False
-            return (self.node == other.node
-                    and self.domain == other.domain
-                    and self.resource == other.resource)
+            return (
+                self.node == other.node
+                and self.domain == other.domain
+                and self.resource == other.resource
+            )
 
         def __hash__(self):
-            return hash('JID<{}>'.format(self.__internal_str))
+            return hash("JID<{}>".format(self.__internal_str))
 
         def find(self, *args):
             return self.__internal_str.find(*args)
 
         def _parse(self):
             """Find node domain and resource"""
-            node_end = self.__internal_str.find('@')
+            node_end = self.__internal_str.find("@")
             if node_end < 0:
                 node_end = 0
-            domain_end = self.__internal_str.find('/')
+            domain_end = self.__internal_str.find("/")
             if domain_end == 0:
                 raise ValueError("a jid can't start with '/'")
             if domain_end == -1:
                 domain_end = len(self.__internal_str)
             self.node = self.__internal_str[:node_end] or None
-            self.domain = self.__internal_str[(node_end + 1) if node_end else 0:domain_end]
-            self.resource = self.__internal_str[domain_end + 1:] or None
+            self.domain = self.__internal_str[
+                (node_end + 1) if node_end else 0 : domain_end
+            ]
+            self.resource = self.__internal_str[domain_end + 1 :] or None
 
 
 class JID(BaseJID):
@@ -98,9 +106,9 @@
         """Naive normalization before instantiating and parsing the JID"""
         if not jid_str:
             return jid_str
-        tokens = jid_str.split('/')
+        tokens = jid_str.split("/")
         tokens[0] = tokens[0].lower()  # force node and domain to lower-case
-        return '/'.join(tokens)
+        return "/".join(tokens)
 
     @property
     def bare(self):
--- a/sat_frontends/tools/misc.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/tools/misc.py	Wed Jun 27 20:14:46 2018 +0200
@@ -19,7 +19,6 @@
 
 
 class InputHistory(object):
-
     def _updateInputHistory(self, text=None, step=None, callback=None, mode=""):
         """Update the lists of previously sent messages. Several lists can be
         handled as they are stored in a dictionary, the argument "mode" being
@@ -92,4 +91,3 @@
     def unused(self):
         """Return flags which has not been used yet"""
         return self.flags.difference(self._used_flags)
-
--- a/sat_frontends/tools/strings.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/tools/strings.py	Wed Jun 27 20:14:46 2018 +0200
@@ -20,7 +20,9 @@
 import re
 
 # Regexp from http://daringfireball.net/2010/07/improved_regex_for_matching_urls
-RE_URL = re.compile(r"""(?i)\b((?:[a-z]{3,}://|(www|ftp)\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/|mailto:|xmpp:)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?]))""")
+RE_URL = re.compile(
+    r"""(?i)\b((?:[a-z]{3,}://|(www|ftp)\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/|mailto:|xmpp:)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?]))"""
+)
 
 
 # TODO: merge this class with an other module or at least rename it (strings is not a good name)
@@ -36,7 +38,7 @@
     dict_ = {}
     if "/" in url:
         # keep the part after the last "/"
-        url = url[url.rindex("/") + 1:]
+        url = url[url.rindex("/") + 1 :]
     if url.startswith("?"):
         # remove the first "?"
         url = url[1:]
@@ -60,8 +62,9 @@
         url = match.group(0)
         if not re.match(r"""[a-z]{3,}://|mailto:|xmpp:""", url):
             url = "http://" + url
-        target = ' target="_blank"' if new_target else ''
+        target = ' target="_blank"' if new_target else ""
         return '<a href="%s"%s class="url">%s</a>' % (url, target, match.group(0))
+
     return RE_URL.sub(repl, string)
 
 
@@ -74,6 +77,7 @@
     def repl(match):
         url = match.group(1)
         return '<a href="%s" target="_blank">%s</a>' % (url, match.group(0))
+
     pattern = r"""<img[^>]* src="([^"]+)"[^>]*>"""
     return re.sub(pattern, repl, string)
 
@@ -96,4 +100,3 @@
     for url, new_url in subs:
         xhtml = xhtml.replace(url, new_url)
     return xhtml
-
--- a/sat_frontends/tools/xmltools.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/tools/xmltools.py	Wed Jun 27 20:14:46 2018 +0200
@@ -29,20 +29,19 @@
     @return: plain XML
     """
     root_elt = doc.documentElement
-    if root_elt.hasAttribute('style'):
-        styles_raw = root_elt.getAttribute('style')
-        styles = styles_raw.split(';')
+    if root_elt.hasAttribute("style"):
+        styles_raw = root_elt.getAttribute("style")
+        styles = styles_raw.split(";")
         new_styles = []
         for style in styles:
             try:
-                key, value = style.split(':')
+                key, value = style.split(":")
             except ValueError:
                 continue
-            if key.strip().lower() ==  'display':
-                value = 'inline'
-            new_styles.append('%s: %s' % (key.strip(), value.strip()))
-        root_elt.setAttribute('style', "; ".join(new_styles))
+            if key.strip().lower() == "display":
+                value = "inline"
+            new_styles.append("%s: %s" % (key.strip(), value.strip()))
+        root_elt.setAttribute("style", "; ".join(new_styles))
     else:
-        root_elt.setAttribute('style', 'display: inline')
+        root_elt.setAttribute("style", "display: inline")
     return root_elt.toxml()
-
--- a/sat_frontends/tools/xmlui.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat_frontends/tools/xmlui.py	Wed Jun 27 20:14:46 2018 +0200
@@ -19,15 +19,17 @@
 
 from sat.core.i18n import _
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 from sat_frontends.quick_frontend.constants import Const as C
 from sat.core import exceptions
 
 
 class_map = {}
-CLASS_PANEL = 'panel'
-CLASS_DIALOG = 'dialog'
-CURRENT_LABEL = 'current_label'
+CLASS_PANEL = "panel"
+CLASS_DIALOG = "dialog"
+CURRENT_LABEL = "current_label"
+
 
 class InvalidXMLUI(Exception):
     pass
@@ -53,31 +55,37 @@
 
 class Widget(object):
     """base Widget"""
+
     pass
 
 
 class EmptyWidget(Widget):
     """Just a placeholder widget"""
+
     pass
 
 
 class TextWidget(Widget):
     """Non interactive text"""
+
     pass
 
 
 class LabelWidget(Widget):
     """Non interactive text"""
+
     pass
 
 
 class JidWidget(Widget):
     """Jabber ID"""
+
     pass
 
 
 class DividerWidget(Widget):
     """Separator"""
+
     pass
 
 
@@ -86,6 +94,7 @@
 
     often called Edit in toolkits
     """
+
     pass
 
 
@@ -94,11 +103,13 @@
 
     often called Edit in toolkits
     """
+
     pass
 
 
 class PasswordWidget(Widget):
     """Input widget with require a masked string"""
+
     pass
 
 
@@ -106,6 +117,7 @@
     """Input widget with require a long, possibly multilines string
     often called TextArea in toolkits
     """
+
     pass
 
 
@@ -113,26 +125,31 @@
     """Input widget with require a boolean value
     often called CheckBox in toolkits
     """
+
     pass
 
 
 class IntWidget(Widget):
     """Input widget with require an integer"""
+
     pass
 
 
 class ButtonWidget(Widget):
     """A clickable widget"""
+
     pass
 
 
 class ListWidget(Widget):
     """A widget able to show/choose one or several strings in a list"""
+
     pass
 
 
 class JidsListWidget(Widget):
     """A widget able to show/choose one or several strings in a list"""
+
     pass
 
 
@@ -152,11 +169,13 @@
 
 class PairsContainer(Container):
     """Widgets are disposed in rows of two (usually label/input)"""
+
     pass
 
 
 class LabelContainer(Container):
     """Widgets are associated with label or empty widget"""
+
     pass
 
 
@@ -165,15 +184,19 @@
 
     Often called Notebook in toolkits
     """
+
     pass
 
+
 class VerticalContainer(Container):
     """Widgets are disposed vertically"""
+
     pass
 
 
 class AdvancedListContainer(Container):
     """Widgets are disposed in rows with advaned features"""
+
     pass
 
 
@@ -206,11 +229,13 @@
 
 class MessageDialog(Dialog):
     """Dialog with a OK/Cancel type configuration"""
+
     pass
 
 
 class NoteDialog(Dialog):
     """Short message which doesn't need user confirmation to disappear"""
+
     pass
 
 
@@ -226,6 +251,7 @@
 
 class FileDialog(Dialog):
     """Dialog with a OK/Cancel type configuration"""
+
     pass
 
 
@@ -235,7 +261,15 @@
     This class must not be instancied directly
     """
 
-    def __init__(self, host, parsed_dom, title=None, flags=None, callback=None, profile=C.PROF_KEY_NONE):
+    def __init__(
+        self,
+        host,
+        parsed_dom,
+        title=None,
+        flags=None,
+        callback=None,
+        profile=C.PROF_KEY_NONE,
+    ):
         """Initialise the XMLUI instance
 
         @param host: %(doc_host)s
@@ -250,7 +284,7 @@
                 dialog closing or new xmlui to display, or other action (you can call host.actionManager)
         """
         self.host = host
-        top=parsed_dom.documentElement
+        top = parsed_dom.documentElement
         self.session_id = top.getAttribute("session_id") or None
         self.submit_id = top.getAttribute("submit") or None
         self.xmlui_title = title or top.getAttribute("title") or u""
@@ -298,13 +332,17 @@
         if self.submit_id is None:
             raise ValueError("Can't submit is self.submit_id is not set")
         if "session_id" in data:
-            raise ValueError("session_id must no be used in data, it is automaticaly filled with self.session_id if present")
+            raise ValueError(
+                "session_id must no be used in data, it is automaticaly filled with self.session_id if present"
+            )
         if self.session_id is not None:
             data["session_id"] = self.session_id
         self._xmluiLaunchAction(self.submit_id, data)
 
     def _xmluiLaunchAction(self, action_id, data):
-        self.host.launchAction(action_id, data, callback=self.callback, profile=self.profile)
+        self.host.launchAction(
+            action_id, data, callback=self.callback, profile=self.profile
+        )
 
     def _xmluiClose(self):
         """Close the window/popup/... where the constructor XMLUI is
@@ -317,7 +355,7 @@
 class ValueGetter(object):
     """dict like object which return values of widgets"""
 
-    def __init__(self, widgets, attr='value'):
+    def __init__(self, widgets, attr="value"):
         self.attr = attr
         self.widgets = widgets
 
@@ -338,18 +376,31 @@
     @property widget_factory: factory to create frontend-specific widgets
     @property dialog_factory: factory to create frontend-specific dialogs
     """
+
     widget_factory = None
 
-    def __init__(self, host, parsed_dom, title=None, flags=None, callback=None, ignore=None, whitelist=None, profile=C.PROF_KEY_NONE):
+    def __init__(
+        self,
+        host,
+        parsed_dom,
+        title=None,
+        flags=None,
+        callback=None,
+        ignore=None,
+        whitelist=None,
+        profile=C.PROF_KEY_NONE,
+    ):
         """
 
         @param title(unicode, None): title of the
         @property widgets(dict): widget name => widget map
         @property widget_value(ValueGetter): retrieve widget value from it's name
         """
-        super(XMLUIPanel, self).__init__(host, parsed_dom, title=title, flags=flags, callback=callback, profile=profile)
+        super(XMLUIPanel, self).__init__(
+            host, parsed_dom, title=title, flags=flags, callback=callback, profile=profile
+        )
         self.ctrl_list = {}  # input widget, used mainly for forms
-        self.widgets = {}  # allow to access any named widgets
+        self.widgets = {}  #  allow to access any named widgets
         self.widget_value = ValueGetter(self.widgets)
         self._main_cont = None
         if ignore is None:
@@ -357,7 +408,9 @@
         self._ignore = ignore
         if whitelist is not None:
             if ignore:
-                raise exceptions.InternalError('ignore and whitelist must not be used at the same time')
+                raise exceptions.InternalError(
+                    "ignore and whitelist must not be used at the same time"
+                )
             self._whitelist = whitelist
         else:
             self._whitelist = None
@@ -377,7 +430,7 @@
             raise ValueError(_("XMLUI can have only one main container"))
         self._main_cont = value
 
-    def _parseChilds(self, _xmlui_parent, current_node, wanted = ('container',), data = None):
+    def _parseChilds(self, _xmlui_parent, current_node, wanted=("container",), data=None):
         """Recursively parse childNodes of an element
 
         @param _xmlui_parent: widget container with '_xmluiAppend' method
@@ -389,82 +442,99 @@
             if data is None:
                 data = {}
             if wanted and not node.nodeName in wanted:
-                raise InvalidXMLUI('Unexpected node: [%s]' % node.nodeName)
+                raise InvalidXMLUI("Unexpected node: [%s]" % node.nodeName)
 
             if node.nodeName == "container":
-                type_ = node.getAttribute('type')
-                if _xmlui_parent is self and type_ != 'vertical':
+                type_ = node.getAttribute("type")
+                if _xmlui_parent is self and type_ != "vertical":
                     # main container is not a VerticalContainer and we want one, so we create one to wrap it
                     _xmlui_parent = self.widget_factory.createVerticalContainer(self)
                     self.main_cont = _xmlui_parent
                 if type_ == "tabs":
                     cont = self.widget_factory.createTabsContainer(_xmlui_parent)
-                    self._parseChilds(_xmlui_parent, node, ('tab',), {'tabs_cont': cont})
+                    self._parseChilds(_xmlui_parent, node, ("tab",), {"tabs_cont": cont})
                 elif type_ == "vertical":
                     cont = self.widget_factory.createVerticalContainer(_xmlui_parent)
-                    self._parseChilds(cont, node, ('widget', 'container'))
+                    self._parseChilds(cont, node, ("widget", "container"))
                 elif type_ == "pairs":
                     cont = self.widget_factory.createPairsContainer(_xmlui_parent)
-                    self._parseChilds(cont, node, ('widget', 'container'))
+                    self._parseChilds(cont, node, ("widget", "container"))
                 elif type_ == "label":
                     cont = self.widget_factory.createLabelContainer(_xmlui_parent)
-                    self._parseChilds(cont, node, ('widget', 'container'), {CURRENT_LABEL: None})
+                    self._parseChilds(
+                        cont, node, ("widget", "container"), {CURRENT_LABEL: None}
+                    )
                 elif type_ == "advanced_list":
                     try:
-                        columns = int(node.getAttribute('columns'))
+                        columns = int(node.getAttribute("columns"))
                     except (TypeError, ValueError):
                         raise exceptions.DataError("Invalid columns")
-                    selectable = node.getAttribute('selectable') or 'no'
-                    auto_index = node.getAttribute('auto_index') == C.BOOL_TRUE
-                    data = {'index': 0} if auto_index else None
-                    cont = self.widget_factory.createAdvancedListContainer(_xmlui_parent, columns, selectable)
+                    selectable = node.getAttribute("selectable") or "no"
+                    auto_index = node.getAttribute("auto_index") == C.BOOL_TRUE
+                    data = {"index": 0} if auto_index else None
+                    cont = self.widget_factory.createAdvancedListContainer(
+                        _xmlui_parent, columns, selectable
+                    )
                     callback_id = node.getAttribute("callback") or None
                     if callback_id is not None:
-                        if selectable == 'no':
-                            raise ValueError("can't have selectable=='no' and callback_id at the same time")
+                        if selectable == "no":
+                            raise ValueError(
+                                "can't have selectable=='no' and callback_id at the same time"
+                            )
                         cont._xmlui_callback_id = callback_id
                         cont._xmluiOnSelect(self.onAdvListSelect)
 
-                    self._parseChilds(cont, node, ('row',), data)
+                    self._parseChilds(cont, node, ("row",), data)
                 else:
                     log.warning(_("Unknown container [%s], using default one") % type_)
                     cont = self.widget_factory.createVerticalContainer(_xmlui_parent)
-                    self._parseChilds(cont, node, ('widget', 'container'))
+                    self._parseChilds(cont, node, ("widget", "container"))
                 try:
                     xmluiAppend = _xmlui_parent._xmluiAppend
-                except (AttributeError, TypeError): # XXX: TypeError is here because pyjamas raise a TypeError instead of an AttributeError
+                except (
+                    AttributeError,
+                    TypeError,
+                ):  # XXX: TypeError is here because pyjamas raise a TypeError instead of an AttributeError
                     if _xmlui_parent is self:
                         self.main_cont = cont
                     else:
-                        raise Exception(_("Internal Error, container has not _xmluiAppend method"))
+                        raise Exception(
+                            _("Internal Error, container has not _xmluiAppend method")
+                        )
                 else:
                     xmluiAppend(cont)
 
-            elif node.nodeName == 'tab':
-                name = node.getAttribute('name')
-                label = node.getAttribute('label')
-                selected = C.bool(node.getAttribute('selected') or C.BOOL_FALSE)
-                if not name or not 'tabs_cont' in data:
+            elif node.nodeName == "tab":
+                name = node.getAttribute("name")
+                label = node.getAttribute("label")
+                selected = C.bool(node.getAttribute("selected") or C.BOOL_FALSE)
+                if not name or not "tabs_cont" in data:
                     raise InvalidXMLUI
-                if self.type == 'param':
-                    self._current_category = name #XXX: awful hack because params need category and we don't keep parent
-                tab_cont = data['tabs_cont']
+                if self.type == "param":
+                    self._current_category = (
+                        name
+                    )  # XXX: awful hack because params need category and we don't keep parent
+                tab_cont = data["tabs_cont"]
                 new_tab = tab_cont._xmluiAddTab(label or name, selected)
-                self._parseChilds(new_tab, node, ('widget', 'container'))
+                self._parseChilds(new_tab, node, ("widget", "container"))
 
-            elif node.nodeName == 'row':
+            elif node.nodeName == "row":
                 try:
-                    index = str(data['index'])
+                    index = str(data["index"])
                 except KeyError:
-                    index = node.getAttribute('index') or None
+                    index = node.getAttribute("index") or None
                 else:
-                    data['index'] += 1
+                    data["index"] += 1
                 _xmlui_parent._xmluiAddRow(index)
-                self._parseChilds(_xmlui_parent, node, ('widget', 'container'))
+                self._parseChilds(_xmlui_parent, node, ("widget", "container"))
 
             elif node.nodeName == "widget":
                 name = node.getAttribute("name")
-                if name and (name in self._ignore or self._whitelist is not None and name not in self._whitelist):
+                if name and (
+                    name in self._ignore
+                    or self._whitelist is not None
+                    and name not in self._whitelist
+                ):
                     # current widget is ignored, but there may be already a label
                     if CURRENT_LABEL in data:
                         # if so, we remove it from parent
@@ -475,79 +545,127 @@
                 if value_elt is not None:
                     value = getText(value_elt)
                 else:
-                    value = node.getAttribute("value") if node.hasAttribute('value') else u''
-                if type_=="empty":
+                    value = (
+                        node.getAttribute("value") if node.hasAttribute("value") else u""
+                    )
+                if type_ == "empty":
                     ctrl = self.widget_factory.createEmptyWidget(_xmlui_parent)
                     if CURRENT_LABEL in data:
                         data[CURRENT_LABEL] = None
-                elif type_=="text":
+                elif type_ == "text":
                     ctrl = self.widget_factory.createTextWidget(_xmlui_parent, value)
-                elif type_=="label":
+                elif type_ == "label":
                     ctrl = self.widget_factory.createLabelWidget(_xmlui_parent, value)
                     data[CURRENT_LABEL] = ctrl
-                elif type_=="jid":
+                elif type_ == "jid":
                     ctrl = self.widget_factory.createJidWidget(_xmlui_parent, value)
-                elif type_=="divider":
-                    style = node.getAttribute("style") or 'line'
+                elif type_ == "divider":
+                    style = node.getAttribute("style") or "line"
                     ctrl = self.widget_factory.createDividerWidget(_xmlui_parent, style)
-                elif type_=="string":
-                    ctrl = self.widget_factory.createStringWidget(_xmlui_parent, value, self._isAttrSet("read_only", node))
-                    self.ctrl_list[name] = ({'type':type_, 'control':ctrl})
-                elif type_=="jid_input":
-                    ctrl = self.widget_factory.createJidInputWidget(_xmlui_parent, value, self._isAttrSet("read_only", node))
-                    self.ctrl_list[name] = ({'type':type_, 'control':ctrl})
-                elif type_=="password":
-                    ctrl = self.widget_factory.createPasswordWidget(_xmlui_parent, value, self._isAttrSet("read_only", node))
-                    self.ctrl_list[name] = ({'type':type_, 'control':ctrl})
-                elif type_=="textbox":
-                    ctrl = self.widget_factory.createTextBoxWidget(_xmlui_parent, value, self._isAttrSet("read_only", node))
-                    self.ctrl_list[name] = ({'type':type_, 'control':ctrl})
-                elif type_=="bool":
-                    ctrl = self.widget_factory.createBoolWidget(_xmlui_parent, value==C.BOOL_TRUE, self._isAttrSet("read_only", node))
-                    self.ctrl_list[name] = ({'type':type_, 'control':ctrl})
+                elif type_ == "string":
+                    ctrl = self.widget_factory.createStringWidget(
+                        _xmlui_parent, value, self._isAttrSet("read_only", node)
+                    )
+                    self.ctrl_list[name] = {"type": type_, "control": ctrl}
+                elif type_ == "jid_input":
+                    ctrl = self.widget_factory.createJidInputWidget(
+                        _xmlui_parent, value, self._isAttrSet("read_only", node)
+                    )
+                    self.ctrl_list[name] = {"type": type_, "control": ctrl}
+                elif type_ == "password":
+                    ctrl = self.widget_factory.createPasswordWidget(
+                        _xmlui_parent, value, self._isAttrSet("read_only", node)
+                    )
+                    self.ctrl_list[name] = {"type": type_, "control": ctrl}
+                elif type_ == "textbox":
+                    ctrl = self.widget_factory.createTextBoxWidget(
+                        _xmlui_parent, value, self._isAttrSet("read_only", node)
+                    )
+                    self.ctrl_list[name] = {"type": type_, "control": ctrl}
+                elif type_ == "bool":
+                    ctrl = self.widget_factory.createBoolWidget(
+                        _xmlui_parent,
+                        value == C.BOOL_TRUE,
+                        self._isAttrSet("read_only", node),
+                    )
+                    self.ctrl_list[name] = {"type": type_, "control": ctrl}
                 elif type_ == "int":
-                    ctrl = self.widget_factory.createIntWidget(_xmlui_parent, value, self._isAttrSet("read_only", node))
-                    self.ctrl_list[name] = ({'type':type_, 'control':ctrl})
+                    ctrl = self.widget_factory.createIntWidget(
+                        _xmlui_parent, value, self._isAttrSet("read_only", node)
+                    )
+                    self.ctrl_list[name] = {"type": type_, "control": ctrl}
                 elif type_ == "list":
-                    style = [] if node.getAttribute("multi") == 'yes' else ['single']
-                    for attr in (u'noselect', u'extensible', u'reducible', u'inline'):
-                        if node.getAttribute(attr) == 'yes':
+                    style = [] if node.getAttribute("multi") == "yes" else ["single"]
+                    for attr in (u"noselect", u"extensible", u"reducible", u"inline"):
+                        if node.getAttribute(attr) == "yes":
                             style.append(attr)
-                    _options = [(option.getAttribute("value"), option.getAttribute("label")) for option in node.getElementsByTagName("option")]
-                    _selected = [option.getAttribute("value") for option in node.getElementsByTagName("option") if option.getAttribute('selected') == C.BOOL_TRUE]
-                    ctrl = self.widget_factory.createListWidget(_xmlui_parent, _options, _selected, style)
-                    self.ctrl_list[name] = ({'type': type_, 'control': ctrl})
+                    _options = [
+                        (option.getAttribute("value"), option.getAttribute("label"))
+                        for option in node.getElementsByTagName("option")
+                    ]
+                    _selected = [
+                        option.getAttribute("value")
+                        for option in node.getElementsByTagName("option")
+                        if option.getAttribute("selected") == C.BOOL_TRUE
+                    ]
+                    ctrl = self.widget_factory.createListWidget(
+                        _xmlui_parent, _options, _selected, style
+                    )
+                    self.ctrl_list[name] = {"type": type_, "control": ctrl}
                 elif type_ == "jids_list":
                     style = []
                     jids = [getText(jid_) for jid_ in node.getElementsByTagName("jid")]
-                    ctrl = self.widget_factory.createJidsListWidget(_xmlui_parent, jids, style)
-                    self.ctrl_list[name] = ({'type': type_, 'control': ctrl})
-                elif type_=="button":
+                    ctrl = self.widget_factory.createJidsListWidget(
+                        _xmlui_parent, jids, style
+                    )
+                    self.ctrl_list[name] = {"type": type_, "control": ctrl}
+                elif type_ == "button":
                     callback_id = node.getAttribute("callback")
-                    ctrl = self.widget_factory.createButtonWidget(_xmlui_parent, value, self.onButtonPress)
-                    ctrl._xmlui_param_id = (callback_id, [field.getAttribute('name') for field in node.getElementsByTagName("field_back")])
+                    ctrl = self.widget_factory.createButtonWidget(
+                        _xmlui_parent, value, self.onButtonPress
+                    )
+                    ctrl._xmlui_param_id = (
+                        callback_id,
+                        [
+                            field.getAttribute("name")
+                            for field in node.getElementsByTagName("field_back")
+                        ],
+                    )
                 else:
-                    log.error(_("FIXME FIXME FIXME: widget type [%s] is not implemented") % type_)
-                    raise NotImplementedError(_("FIXME FIXME FIXME: type [%s] is not implemented") % type_)
+                    log.error(
+                        _("FIXME FIXME FIXME: widget type [%s] is not implemented")
+                        % type_
+                    )
+                    raise NotImplementedError(
+                        _("FIXME FIXME FIXME: type [%s] is not implemented") % type_
+                    )
 
                 if name:
                     self.widgets[name] = ctrl
 
-                if self.type == 'param' and type_ not in ('text', 'button'):
+                if self.type == "param" and type_ not in ("text", "button"):
                     try:
                         ctrl._xmluiOnChange(self.onParamChange)
                         ctrl._param_category = self._current_category
-                    except (AttributeError, TypeError):  # XXX: TypeError is here because pyjamas raise a TypeError instead of an AttributeError
-                        if not isinstance(ctrl, (EmptyWidget, TextWidget, LabelWidget, JidWidget)):
+                    except (
+                        AttributeError,
+                        TypeError,
+                    ):  # XXX: TypeError is here because pyjamas raise a TypeError instead of an AttributeError
+                        if not isinstance(
+                            ctrl, (EmptyWidget, TextWidget, LabelWidget, JidWidget)
+                        ):
                             log.warning(_("No change listener on [%s]") % ctrl)
 
-                elif type_ != 'text':
+                elif type_ != "text":
                     callback = node.getAttribute("internal_callback") or None
                     if callback:
-                        fields = [field.getAttribute('name') for field in node.getElementsByTagName("internal_field")]
+                        fields = [
+                            field.getAttribute("name")
+                            for field in node.getElementsByTagName("internal_field")
+                        ]
                         cb_data = self.getInternalCallbackData(callback, node)
                         ctrl._xmlui_param_internal = (callback, fields, cb_data)
-                        if type_ == 'button':
+                        if type_ == "button":
                             ctrl._xmluiOnClick(self.onChangeInternal)
                         else:
                             ctrl._xmluiOnChange(self.onChangeInternal)
@@ -560,7 +678,7 @@
                     data.pop(CURRENT_LABEL)._xmlui_for_name = name
 
             else:
-                raise NotImplementedError(_('Unknown tag [%s]') % node.nodeName)
+                raise NotImplementedError(_("Unknown tag [%s]") % node.nodeName)
 
     def constructUI(self, parsed_dom, post_treat=None):
         """Actually construct the UI
@@ -569,12 +687,17 @@
         @param post_treat: frontend specific treatments to do once the UI is constructed
         @return: constructed widget
         """
-        top=parsed_dom.documentElement
+        top = parsed_dom.documentElement
         self.type = top.getAttribute("type")
-        if top.nodeName != "sat_xmlui" or not self.type in ['form', 'param', 'window', 'popup']:
+        if top.nodeName != "sat_xmlui" or not self.type in [
+            "form",
+            "param",
+            "window",
+            "popup",
+        ]:
             raise InvalidXMLUI
 
-        if self.type == 'param':
+        if self.type == "param":
             self.param_changed = set()
 
         self._parseChilds(self, parsed_dom.documentElement)
@@ -603,11 +726,14 @@
                 name = self.escape(wid._xmlui_name)
                 value = wid._xmluiGetValue()
                 data[name] = value
-            except (AttributeError, TypeError): # XXX: TypeError is here because pyjamas raise a TypeError instead of an AttributeError
+            except (
+                AttributeError,
+                TypeError,
+            ):  # XXX: TypeError is here because pyjamas raise a TypeError instead of an AttributeError
                 pass
         idx = ctrl._xmluiGetSelectedIndex()
         if idx is not None:
-            data['index'] = idx
+            data["index"] = idx
         callback_id = ctrl._xmlui_callback_id
         if callback_id is None:
             log.info(_("No callback_id found"))
@@ -627,10 +753,10 @@
         for field in fields:
             escaped = self.escape(field)
             ctrl = self.ctrl_list[field]
-            if isinstance(ctrl['control'], ListWidget):
-                data[escaped] = u'\t'.join(ctrl['control']._xmluiGetSelectedValues())
+            if isinstance(ctrl["control"], ListWidget):
+                data[escaped] = u"\t".join(ctrl["control"]._xmluiGetSelectedValues())
             else:
-                data[escaped] = ctrl['control']._xmluiGetValue()
+                data[escaped] = ctrl["control"]._xmluiGetValue()
         self._xmluiLaunchAction(callback_id, data)
 
     def onChangeInternal(self, ctrl):
@@ -641,8 +767,10 @@
         @param ctrl: widget modified
         """
         action, fields, data = ctrl._xmlui_param_internal
-        if action not in ('copy', 'move', 'groups_of_contact'):
-            raise NotImplementedError(_("FIXME: XMLUI internal action [%s] is not implemented") % action)
+        if action not in ("copy", "move", "groups_of_contact"):
+            raise NotImplementedError(
+                _("FIXME: XMLUI internal action [%s] is not implemented") % action
+            )
 
         def copy_move(source, target):
             """Depending of 'action' value, copy or move from source to target."""
@@ -651,18 +779,18 @@
                     values = source._xmluiGetSelectedValues()
                 else:
                     values = [source._xmluiGetValue()]
-                    if action == 'move':
-                        source._xmluiSetValue('')
+                    if action == "move":
+                        source._xmluiSetValue("")
                 values = [value for value in values if value]
                 if values:
                     target._xmluiAddValues(values, select=True)
             else:
                 if isinstance(source, ListWidget):
-                    value = u', '.join(source._xmluiGetSelectedValues())
+                    value = u", ".join(source._xmluiGetSelectedValues())
                 else:
                     value = source._xmluiGetValue()
-                    if action == 'move':
-                        source._xmluiSetValue('')
+                    if action == "move":
+                        source._xmluiSetValue("")
                 target._xmluiSetValue(value)
 
         def groups_of_contact(source, target):
@@ -678,13 +806,13 @@
 
         source = None
         for field in fields:
-            widget = self.ctrl_list[field]['control']
+            widget = self.ctrl_list[field]["control"]
             if not source:
                 source = widget
                 continue
-            if action in ('copy', 'move'):
+            if action in ("copy", "move"):
                 copy_move(source, widget)
-            elif action == 'groups_of_contact':
+            elif action == "groups_of_contact":
                 groups_of_contact(source, widget)
             source = None
 
@@ -700,16 +828,18 @@
         # extract any kind of data structure from the 'internal_data' element.
 
         try:  # data is stored in the first 'internal_data' element of the node
-            data_elts = node.getElementsByTagName('internal_data')[0].childNodes
+            data_elts = node.getElementsByTagName("internal_data")[0].childNodes
         except IndexError:
             return None
         data = {}
-        if action == 'groups_of_contact':  # return a dict(key: string, value: list[string])
+        if (
+            action == "groups_of_contact"
+        ):  # return a dict(key: string, value: list[string])
             for elt in data_elts:
-                jid_s = elt.getAttribute('name')
+                jid_s = elt.getAttribute("name")
                 data[jid_s] = []
                 for value_elt in elt.childNodes:
-                    data[jid_s].append(value_elt.getAttribute('name'))
+                    data[jid_s].append(value_elt.getAttribute("name"))
         return data
 
     def onFormSubmitted(self, ignore=None):
@@ -721,15 +851,19 @@
         for ctrl_name in self.ctrl_list:
             escaped = self.escape(ctrl_name)
             ctrl = self.ctrl_list[ctrl_name]
-            if isinstance(ctrl['control'], ListWidget):
-                selected_values.append((escaped, u'\t'.join(ctrl['control']._xmluiGetSelectedValues())))
+            if isinstance(ctrl["control"], ListWidget):
+                selected_values.append(
+                    (escaped, u"\t".join(ctrl["control"]._xmluiGetSelectedValues()))
+                )
             else:
-                selected_values.append((escaped, ctrl['control']._xmluiGetValue()))
+                selected_values.append((escaped, ctrl["control"]._xmluiGetValue()))
         if self.submit_id is not None:
             data = dict(selected_values)
             self.submit(data)
         else:
-            log.warning(_("The form data is not sent back, the type is not managed properly"))
+            log.warning(
+                _("The form data is not sent back, the type is not managed properly")
+            )
             self._xmluiClose()
 
     def onFormCancelled(self, ignore=None):
@@ -739,7 +873,9 @@
             data = {C.XMLUI_DATA_CANCELLED: C.BOOL_TRUE}
             self.submit(data)
         else:
-            log.warning(_("The form data is not sent back, the type is not managed properly"))
+            log.warning(
+                _("The form data is not sent back, the type is not managed properly")
+            )
         self._xmluiClose()
 
     def onSaveParams(self, ignore=None):
@@ -747,10 +883,10 @@
 
         self.type must be param
         """
-        assert self.type == 'param'
+        assert self.type == "param"
         for ctrl in self.param_changed:
             if isinstance(ctrl, ListWidget):
-                value = u'\t'.join(ctrl._xmluiGetSelectedValues())
+                value = u"\t".join(ctrl._xmluiGetSelectedValues())
             else:
                 value = ctrl._xmluiGetValue()
             param_name = ctrl._xmlui_name.split(C.SAT_PARAM_SEPARATOR)[1]
@@ -765,38 +901,69 @@
 class XMLUIDialog(XMLUIBase):
     dialog_factory = None
 
-    def __init__(self, host, parsed_dom, title=None, flags=None, callback=None, ignore=None, whitelist=None, profile=C.PROF_KEY_NONE):
-        super(XMLUIDialog, self).__init__(host, parsed_dom, title=title, flags=flags, callback=callback, profile=profile)
-        top=parsed_dom.documentElement
-        dlg_elt =  self._getChildNode(top, "dialog")
+    def __init__(
+        self,
+        host,
+        parsed_dom,
+        title=None,
+        flags=None,
+        callback=None,
+        ignore=None,
+        whitelist=None,
+        profile=C.PROF_KEY_NONE,
+    ):
+        super(XMLUIDialog, self).__init__(
+            host, parsed_dom, title=title, flags=flags, callback=callback, profile=profile
+        )
+        top = parsed_dom.documentElement
+        dlg_elt = self._getChildNode(top, "dialog")
         if dlg_elt is None:
             raise ValueError("Invalid XMLUI: no Dialog element found !")
         dlg_type = dlg_elt.getAttribute("type") or C.XMLUI_DIALOG_MESSAGE
         try:
             mess_elt = self._getChildNode(dlg_elt, C.XMLUI_DATA_MESS)
             message = getText(mess_elt)
-        except (TypeError, AttributeError): # XXX: TypeError is here because pyjamas raise a TypeError instead of an AttributeError
+        except (
+            TypeError,
+            AttributeError,
+        ):  # XXX: TypeError is here because pyjamas raise a TypeError instead of an AttributeError
             message = ""
         level = dlg_elt.getAttribute(C.XMLUI_DATA_LVL) or C.XMLUI_DATA_LVL_INFO
 
         if dlg_type == C.XMLUI_DIALOG_MESSAGE:
-            self.dlg = self.dialog_factory.createMessageDialog(self, self.xmlui_title, message, level)
+            self.dlg = self.dialog_factory.createMessageDialog(
+                self, self.xmlui_title, message, level
+            )
         elif dlg_type == C.XMLUI_DIALOG_NOTE:
-            self.dlg = self.dialog_factory.createNoteDialog(self, self.xmlui_title, message, level)
+            self.dlg = self.dialog_factory.createNoteDialog(
+                self, self.xmlui_title, message, level
+            )
         elif dlg_type == C.XMLUI_DIALOG_CONFIRM:
             try:
                 buttons_elt = self._getChildNode(dlg_elt, "buttons")
-                buttons_set = buttons_elt.getAttribute("set") or C.XMLUI_DATA_BTNS_SET_DEFAULT
-            except (TypeError, AttributeError): # XXX: TypeError is here because pyjamas raise a TypeError instead of an AttributeError
+                buttons_set = (
+                    buttons_elt.getAttribute("set") or C.XMLUI_DATA_BTNS_SET_DEFAULT
+                )
+            except (
+                TypeError,
+                AttributeError,
+            ):  # XXX: TypeError is here because pyjamas raise a TypeError instead of an AttributeError
                 buttons_set = C.XMLUI_DATA_BTNS_SET_DEFAULT
-            self.dlg = self.dialog_factory.createConfirmDialog(self, self.xmlui_title, message, level, buttons_set)
+            self.dlg = self.dialog_factory.createConfirmDialog(
+                self, self.xmlui_title, message, level, buttons_set
+            )
         elif dlg_type == C.XMLUI_DIALOG_FILE:
             try:
                 file_elt = self._getChildNode(dlg_elt, "file")
                 filetype = file_elt.getAttribute("type") or C.XMLUI_DATA_FILETYPE_DEFAULT
-            except (TypeError, AttributeError): # XXX: TypeError is here because pyjamas raise a TypeError instead of an AttributeError
+            except (
+                TypeError,
+                AttributeError,
+            ):  # XXX: TypeError is here because pyjamas raise a TypeError instead of an AttributeError
                 filetype = C.XMLUI_DATA_FILETYPE_DEFAULT
-            self.dlg = self.dialog_factory.createFileDialog(self, self.xmlui_title, message, level, filetype)
+            self.dlg = self.dialog_factory.createFileDialog(
+                self, self.xmlui_title, message, level, filetype
+            )
         else:
             raise ValueError("Unknown dialog type [%s]" % dlg_type)
 
@@ -819,7 +986,18 @@
     class_map[type_] = class_
 
 
-def create(host, xml_data, title=None, flags=None, dom_parse=None, dom_free=None, callback=None, ignore=None, whitelist=None, profile=C.PROF_KEY_NONE):
+def create(
+    host,
+    xml_data,
+    title=None,
+    flags=None,
+    dom_parse=None,
+    dom_free=None,
+    callback=None,
+    ignore=None,
+    whitelist=None,
+    profile=C.PROF_KEY_NONE,
+):
     """
         @param dom_parse: methode equivalent to minidom.parseString (but which must manage unicode), or None to use default one
         @param dom_free: method used to free the parsed DOM
@@ -831,13 +1009,14 @@
     """
     if dom_parse is None:
         from xml.dom import minidom
-        dom_parse = lambda xml_data: minidom.parseString(xml_data.encode('utf-8'))
+
+        dom_parse = lambda xml_data: minidom.parseString(xml_data.encode("utf-8"))
         dom_free = lambda parsed_dom: parsed_dom.unlink()
     else:
         dom_parse = dom_parse
         dom_free = dom_free or (lambda parsed_dom: None)
     parsed_dom = dom_parse(xml_data)
-    top=parsed_dom.documentElement
+    top = parsed_dom.documentElement
     ui_type = top.getAttribute("type")
     try:
         if ui_type != C.XMLUI_DIALOG:
@@ -845,14 +1024,19 @@
         else:
             cls = class_map[CLASS_DIALOG]
     except KeyError:
-        raise ClassNotRegistedError(_("You must register classes with registerClass before creating a XMLUI"))
+        raise ClassNotRegistedError(
+            _("You must register classes with registerClass before creating a XMLUI")
+        )
 
-    xmlui = cls(host, parsed_dom,
-                title = title,
-                flags = flags,
-                callback = callback,
-                ignore = ignore,
-                whitelist = whitelist,
-                profile = profile)
+    xmlui = cls(
+        host,
+        parsed_dom,
+        title=title,
+        flags=flags,
+        callback=callback,
+        ignore=ignore,
+        whitelist=whitelist,
+        profile=profile,
+    )
     dom_free(parsed_dom)
     return xmlui