diff sat/tools/xml_tools.py @ 2762:5a51c7fc74a5

core (XMLUI): small optimisation: introspection is done once at module loading instead of on each XMLUI instantiation
author Goffi <goffi@goffi.org>
date Fri, 11 Jan 2019 09:48:17 +0100
parents 1797671827b9
children 003b8b4b56a7
line wrap: on
line diff
--- a/sat/tools/xml_tools.py	Sun Jan 06 17:39:57 2019 +0100
+++ b/sat/tools/xml_tools.py	Fri Jan 11 09:48:17 2019 +0100
@@ -110,7 +110,6 @@
 
     return widget_type, widget_args, widget_kwargs
 
-
 def dataForm2Widgets(form_ui, form, read_only=False, prepend=None, filters=None):
     """Complete an existing XMLUI with widget converted from XEP-0004 data forms.
 
@@ -280,7 +279,11 @@
     @param filters: same as for [dataForm2Widgets]
     @return: XMLUI instance
     """
+    # we deepcopy the form because _dataFormField2XMLUIData can modify the value
+    # FIXME: check if it's really important, the only modified value seems to be
+    #        the replacement of None by "" on fixed fields
     form = deepcopy(result_form)
+    form = result_form
     for name, field in form.fields.iteritems():
         try:
             base_field = base_form.fields[name]
@@ -1300,7 +1303,6 @@
                            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,
@@ -1336,50 +1338,63 @@
         self.current_container = self.main_container
         self.named_widgets = {}
 
-    def _introspect(self):
-        """ Introspect module to find Widgets and Containers """
-        self._containers = {}
-        self._widgets = {}
+    @staticmethod
+    def creatorWrapper(widget_cls, is_input):
+        # TODO: once moved to Python 3, use functools.partialmethod and
+        #       remove the creatorWrapper
+        def createWidget(self, *args, **kwargs):
+            if self.type == C.XMLUI_DIALOG:
+                raise exceptions.InternalError(_(
+                    "createWidget can't be used with dialogs"))
+            if "parent" not in kwargs:
+                kwargs["parent"] = self.current_container
+            if "name" not in kwargs and is_input:
+                # name can be given as first argument or in keyword
+                # arguments for InputWidgets
+                args = list(args)
+                kwargs["name"] = args.pop(0)
+            return widget_cls(self, *args, **kwargs)
+        return createWidget
+
+    @classmethod
+    def _introspect(cls):
+        """ Introspect module to find Widgets and Containers, and create addXXX methods"""
+        # FIXME: we can't log anything because this file is used
+        #        in bin/sat script then evaluated
+        #        bin/sat should be refactored
+        # log.debug(u'introspecting XMLUI widgets and containers')
+        cls._containers = {}
+        cls._widgets = {}
         for obj in globals().values():
             try:
                 if issubclass(obj, Widget):
                     if obj.__name__ == "Widget":
                         continue
-                    self._widgets[obj.type] = obj
+                    cls._widgets[obj.type] = obj
+                    creator_name = u"add" + obj.__name__
+                    if creator_name.endswith('Widget'):
+                        creator_name = creator_name[:-6]
+                    is_input = issubclass(obj, InputWidget)
+                    # FIXME: cf. above comment
+                    # log.debug(u"Adding {creator_name} creator (is_input={is_input}))"
+                    #     .format(creator_name=creator_name, is_input=is_input))
+
+                    assert not hasattr(cls, creator_name)
+                    # XXX: we need to use creatorWrapper because we are in a loop
+                    #      and Python 2 doesn't support default values in kwargs
+                    #      when using *args, **kwargs
+                    setattr(cls, creator_name, cls.creatorWrapper(obj, is_input))
+
                 elif issubclass(obj, Container):
                     if obj.__name__ == "Container":
                         continue
-                    self._containers[obj.type] = obj
+                    cls._containers[obj.type] = obj
             except TypeError:
                 pass
 
     def __del__(self):
         self.doc.unlink()
 
-    def __getattr__(self, name):
-        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
-                            args = list(args)
-                            kwargs["name"] = args.pop(0)
-                        return cls(self, *args, **kwargs)
-
-                    return createWidget
-        return object.__getattribute__(self, name)
-
     @property
     def submit_id(self):
         top_element = self.doc.documentElement
@@ -1480,11 +1495,12 @@
 
     def addWidget(self, type_, *args, **kwargs):
         """Convenience method to add an element"""
-        if type_ not in self._widgets:
-            raise exceptions.DataError(_("Invalid type [%s]") % type_)
         if "parent" not in kwargs:
             kwargs["parent"] = self.current_container
-        cls = self._widgets[type_]
+        try:
+            cls = self._widgets[type_]
+        except KeyError:
+            raise exceptions.DataError(_("Invalid type [{type_}]").format(type_=type_))
         return cls(self, *args, **kwargs)
 
     def toXml(self):
@@ -1492,6 +1508,10 @@
         return self.doc.toxml()
 
 
+# we call this to have automatic discovery of containers and widgets
+XMLUI._introspect()
+
+
 # Some sugar for XMLUI dialogs