source: orange/Orange/OrangeCanvas/orngRegistry.py @ 10824:12f8bcef033a

Revision 10824:12f8bcef033a, 10.5 KB checked in by Ales Erjavec <ales.erjavec@…>, 2 years ago (diff)

Increase cached widget description version number.

Line 
1# Author: Gregor Leban (gregor.leban@fri.uni-lj.si)
2#
3
4import os, sys, re, glob, stat
5from orngSignalManager import OutputSignal, InputSignal, resolveSignal
6from PyQt4.QtCore import *
7from PyQt4.QtGui import *
8import widgetParser
9
10orangeDir = os.path.split(os.path.split(os.path.abspath(__file__))[0])[0]
11if not orangeDir in sys.path:
12    sys.path.append(orangeDir)
13
14import orngEnviron, Orange.utils.addons
15
16class WidgetDescription:
17    def __init__(self, **attrs):
18        self.__dict__.update(attrs)
19
20    def docDir(self):
21        if not self.addOn:  # A built-in widget
22            dir, widgetDir = os.path.realpath(self.directory), os.path.realpath(orngEnviron.widgetDir)
23            subDir = os.path.relpath(dir, widgetDir) if "relpath" in os.path.__dict__ else dir.replace(widgetDir, "")
24            return os.path.join(orngEnviron.orangeDocDir, "widgets", subDir)
25        else:  # An add-on widget
26            addOnDocDir = self.addOn.directory_documentation()
27            return os.path.join(addOnDocDir, "widgets")
28
29
30class WidgetCategory(dict):
31    def __init__(self, name, widgets=None):
32        if widgets:
33            self.update(widgets)
34        self.name = name
35   
36def readCategories(silent=False):
37    try:
38        from Orange.version import version as orange_version
39    except ImportError:
40        # Orange.version module is writen by setup.py, what if orange was build
41        # using make
42        orange_version = "???"
43    # Add orange version to the cache version (because cache contains names
44    # of types inside the Orange hierarchy, if that changes the cache should be
45    # invalidated)
46    currentCacheVersion = (3, orange_version)
47   
48    global widgetsWithError, widgetsWithErrorPrototypes
49    widgetDirName = os.path.realpath(orngEnviron.directoryNames["widgetDir"])
50    canvasSettingsDir = os.path.realpath(orngEnviron.directoryNames["canvasSettingsDir"])
51    cacheFilename = os.path.join(canvasSettingsDir, "cachedWidgetDescriptions.pickle")
52
53    try:
54        import cPickle
55        cacheFile = file(cacheFilename, "rb")
56        cats = cPickle.load(cacheFile)
57        try:
58            version = cPickle.load(cacheFile)
59        except EOFError:
60            version = 0
61        if version == currentCacheVersion:
62            cachedWidgetDescriptions = dict([(w.fullName, w) for cat in cats.values() for w in cat.values()])
63        else:
64            cachedWidgetDescriptions = {}
65    except Exception:
66        cachedWidgetDescriptions = {} 
67
68    directories = [] # tuples (defaultCategory, dirName, plugin, isPrototype)
69    for dirName in os.listdir(widgetDirName):
70        directory = os.path.join(widgetDirName, dirName)
71        if os.path.isdir(directory):
72            directories.append((None, directory, None, "prototypes" in dirName.lower()))
73           
74    # read list of add-ons
75    for addOn in Orange.utils.addons.installed_addons.values() + Orange.utils.addons.registered_addons:
76        addOnWidgetsDir = os.path.join(addOn.directory, "widgets")
77        if os.path.isdir(addOnWidgetsDir):
78            directories.append((addOn.name, addOnWidgetsDir, addOn, False))
79        addOnWidgetsPrototypesDir = os.path.join(addOnWidgetsDir, "prototypes")
80        if os.path.isdir(addOnWidgetsPrototypesDir):
81            directories.append((None, addOnWidgetsPrototypesDir, addOn, True))
82
83    categories = {}     
84    for defCat, dirName, addOn, isPrototype in directories:
85        widgets = readWidgets(dirName, cachedWidgetDescriptions, isPrototype, silent=silent, addOn=addOn, defaultCategory=defCat)
86        for (wName, wInfo) in widgets:
87            catName = wInfo.category
88            if not catName in categories:
89                categories[catName] = WidgetCategory(catName)
90            if wName in categories[catName]:
91                print "Warning! A widget with duplicated name '%s' in category '%s' has been found! It will _not_ be available in the Canvas." % (wName, catName)
92            else:
93                categories[catName][wName] = wInfo
94
95    cacheFile = file(cacheFilename, "wb")
96    cPickle.dump(categories, cacheFile)
97    cPickle.dump(currentCacheVersion, cacheFile)
98    if splashWindow:
99        splashWindow.hide()
100
101    if not silent:
102        if widgetsWithError != []:
103            print "The following widgets could not be imported and will not be available: " + ", ".join(set(widgetsWithError)) + "."
104        if widgetsWithErrorPrototypes != []:
105            print "The following prototype widgets could not be imported and will not be available: " + ", ".join(set(widgetsWithErrorPrototypes)) + "."
106
107    return categories
108
109
110hasErrors = False
111splashWindow = None
112widgetsWithError = []
113widgetsWithErrorPrototypes = []
114
115def readWidgets(directory, cachedWidgetDescriptions, prototype=False, silent=False, addOn=None, defaultCategory=None):
116    import sys, imp
117    global hasErrors, splashWindow, widgetsWithError, widgetsWithErrorPrototypes
118   
119    widgets = []
120   
121    if not defaultCategory:
122        predir, defaultCategory = os.path.split(directory.strip(os.path.sep).strip(os.path.altsep))
123        if defaultCategory == "widgets":
124            defaultCategory = os.path.split(predir.strip(os.path.sep).strip(os.path.altsep))[1]
125   
126    if defaultCategory.lower() == "prototypes" or prototype:
127        defaultCategory = "Prototypes"
128   
129    for filename in glob.iglob(os.path.join(directory, "*.py")):
130        if os.path.isdir(filename):
131            continue
132       
133        datetime = str(os.stat(filename)[stat.ST_MTIME])
134        cachedDescription = cachedWidgetDescriptions.get(filename, None)
135        if cachedDescription and cachedDescription.time == datetime and hasattr(cachedDescription, "inputClasses"):
136            widgets.append((cachedDescription.name, cachedDescription))
137            continue
138       
139        data = file(filename).read()
140        try:
141            meta = widgetParser.WidgetMetaData(data, defaultCategory, enforceDefaultCategory=prototype)
142        except:   # Probably not an Orange widget module.
143            continue
144
145        dirname, fname = os.path.split(filename)
146        widgname = os.path.splitext(fname)[0]
147        try:
148            if not splashWindow:
149                import orngEnviron
150                logo = QPixmap(os.path.join(orngEnviron.directoryNames["canvasDir"], "icons", "splash.png"))
151                splashWindow = QSplashScreen(logo, Qt.WindowStaysOnTopHint)
152                splashWindow.setMask(logo.mask())
153                splashWindow.show()
154               
155            splashWindow.showMessage("Registering widget %s" % meta.name, Qt.AlignHCenter + Qt.AlignBottom)
156            qApp.processEvents()
157           
158            # We import modules using imp.load_source to avoid storing them in sys.modules,
159            # but we need to append the path to sys.path in case the module would want to load
160            # something
161            dirnameInPath = dirname in sys.path
162            if not dirnameInPath:
163                sys.path.append(dirname)
164            wmod = imp.load_source(widgname, filename)
165            if not dirnameInPath and dirname in sys.path: # I have no idea, why we need this, but it seems to disappear sometimes?!
166                sys.path.remove(dirname)
167            widgClass = wmod.__dict__[widgname]
168
169            # Evaluate the input/output list (all tuple items are strings)
170            inputs = eval(meta.inputList)
171            outputs = eval(meta.outputList)
172
173            inputs = [InputSignal(*input) for input in inputs]
174            outputs = [OutputSignal(*output) for output in outputs]
175
176            # Resolve signal type names into concrete type instances
177            inputs = [resolveSignal(input, globals=wmod.__dict__)
178                      for input in inputs]
179            outputs = [resolveSignal(output, globals=wmod.__dict__)
180                      for output in outputs]
181
182            inputClasses = set([s.type.__name__ for s in inputs])
183            outputClasses = set([klass.__name__ for s in outputs
184                                 for klass in s.type.mro()])
185
186            # Convert all signal types back into qualified names.
187            # This is to prevent any possible import problems when cached
188            # descriptions are unpickled (the relevant code using this lists
189            # should be able to handle missing types better).
190            for s in inputs + outputs:
191                s.type = "%s.%s" % (s.type.__module__, s.type.__name__)
192
193            widgetInfo = WidgetDescription(
194                             name =meta.name,
195                             time = datetime,
196                             fileName = widgname,
197                             fullName = filename,
198                             directory = directory,
199                             addOn = addOn,
200                             inputList = meta.inputList, outputList = meta.outputList,
201                             inputClasses = inputClasses, outputClasses = outputClasses,
202                             tags=meta.tags,
203                             inputs=inputs,
204                             outputs=outputs,
205                             )
206
207            for attr in ["contact", "icon", "priority", "description", "category"]:
208                setattr(widgetInfo, attr, getattr(meta, attr))
209
210            # build the tooltip
211            if len(widgetInfo.inputs) == 0:
212                formatedInList = "<b>Inputs:</b><br> &nbsp;&nbsp; None<br>"
213            else:
214                formatedInList = "<b>Inputs:</b><br>"
215                for signal in widgetInfo.inputs:
216                    formatedInList += " &nbsp;&nbsp; - " + signal.name + " (" + signal.type + ")<br>"
217
218            if len(widgetInfo.outputs) == 0:
219                formatedOutList = "<b>Outputs:</b><br> &nbsp; &nbsp; None<br>"
220            else:
221                formatedOutList = "<b>Outputs:</b><br>"
222                for signal in widgetInfo.outputs:
223                    formatedOutList += " &nbsp; &nbsp; - " + signal.name + " (" + signal.type + ")<br>"
224
225            addOnName = "" if not widgetInfo.addOn else " (from add-on %s)" % widgetInfo.addOn.name
226   
227            widgetInfo.tooltipText = "<b><b>&nbsp;%s</b></b>%s<hr><b>Description:</b><br>&nbsp;&nbsp;%s<hr>%s<hr>%s" % (meta.name, addOnName, widgetInfo.description, formatedInList[:-4], formatedOutList[:-4]) 
228            widgets.append((meta.name, widgetInfo))
229        except Exception, msg:
230            if not hasErrors and not silent:
231                print "There were problems importing the following widgets:"
232                hasErrors = True
233            if not silent:
234                print "   %s: %s" % (widgname, msg)
235
236            if not prototype:
237                widgetsWithError.append(widgname)
238            else:
239                widgetsWithErrorPrototypes.append(widgname)
240       
241    return widgets
Note: See TracBrowser for help on using the repository browser.