source: sasview/src/sas/sasgui/perspectives/fitting/models.py @ 92eee84

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalcmagnetic_scattrelease-4.2.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 92eee84 was 632fda9, checked in by lewis, 7 years ago

Fix old style plugin models not causing sasview to run

  • Property mode set to 100644
File size: 16.2 KB
Line 
1"""
2    Utilities to manage models
3"""
4from __future__ import print_function
5
6import traceback
7import os
8import sys
9import os.path
10# Time is needed by the log method
11import time
12import datetime
13import logging
14import py_compile
15import shutil
16from copy import copy
17# Explicitly import from the pluginmodel module so that py2exe
18# places it in the distribution. The Model1DPlugin class is used
19# as the base class of plug-in models.
20from sas.sascalc.fit.pluginmodel import Model1DPlugin
21from sas.sasgui.guiframe.CategoryInstaller import CategoryInstaller
22from sasmodels.sasview_model import load_custom_model, load_standard_models
23
24logger = logging.getLogger(__name__)
25
26
27PLUGIN_DIR = 'plugin_models'
28PLUGIN_LOG = os.path.join(os.path.expanduser("~"), '.sasview', PLUGIN_DIR,
29                          "plugins.log")
30PLUGIN_NAME_BASE = '[plug-in] '
31
32def get_model_python_path():
33    """
34    Returns the python path for a model
35    """
36    return os.path.dirname(__file__)
37
38
39def plugin_log(message):
40    """
41    Log a message in a file located in the user's home directory
42    """
43    out = open(PLUGIN_LOG, 'a')
44    now = time.time()
45    stamp = datetime.datetime.fromtimestamp(now).strftime('%Y-%m-%d %H:%M:%S')
46    out.write("%s: %s\n" % (stamp, message))
47    out.close()
48
49
50def _check_plugin(model, name):
51    """
52    Do some checking before model adding plugins in the list
53
54    :param model: class model to add into the plugin list
55    :param name:name of the module plugin
56
57    :return model: model if valid model or None if not valid
58
59    """
60    #Check if the plugin is of type Model1DPlugin
61    if not issubclass(model, Model1DPlugin):
62        msg = "Plugin %s must be of type Model1DPlugin \n" % str(name)
63        plugin_log(msg)
64        return None
65    if model.__name__ != "Model":
66        msg = "Plugin %s class name must be Model \n" % str(name)
67        plugin_log(msg)
68        return None
69    try:
70        new_instance = model()
71    except:
72        msg = "Plugin %s error in __init__ \n\t: %s %s\n" % (str(name),
73                                                             str(sys.exc_type),
74                                                             sys.exc_info()[1])
75        plugin_log(msg)
76        return None
77
78    if hasattr(new_instance, "function"):
79        try:
80            value = new_instance.function()
81        except:
82            msg = "Plugin %s: error writing function \n\t :%s %s\n " % \
83                    (str(name), str(sys.exc_type), sys.exc_info()[1])
84            plugin_log(msg)
85            return None
86    else:
87        msg = "Plugin  %s needs a method called function \n" % str(name)
88        plugin_log(msg)
89        return None
90    return model
91
92
93def find_plugins_dir():
94    """
95    Find path of the plugins directory.
96    The plugin directory is located in the user's home directory.
97    """
98    dir = os.path.join(os.path.expanduser("~"), '.sasview', PLUGIN_DIR)
99
100    # If the plugin directory doesn't exist, create it
101    if not os.path.isdir(dir):
102        os.makedirs(dir)
103
104    # Find paths needed
105    try:
106        # For source
107        if os.path.isdir(os.path.dirname(__file__)):
108            p_dir = os.path.join(os.path.dirname(__file__), PLUGIN_DIR)
109        else:
110            raise
111    except:
112        # Check for data path next to exe/zip file.
113        #Look for maximum n_dir up of the current dir to find plugins dir
114        n_dir = 12
115        p_dir = None
116        f_dir = os.path.join(os.path.dirname(__file__))
117        for i in range(n_dir):
118            if i > 1:
119                f_dir, _ = os.path.split(f_dir)
120            plugin_path = os.path.join(f_dir, PLUGIN_DIR)
121            if os.path.isdir(plugin_path):
122                p_dir = plugin_path
123                break
124        if not p_dir:
125            raise
126    # Place example user models as needed
127    if os.path.isdir(p_dir):
128        for file in os.listdir(p_dir):
129            file_path = os.path.join(p_dir, file)
130            if os.path.isfile(file_path):
131                if file.split(".")[-1] == 'py' and\
132                    file.split(".")[0] != '__init__':
133                    if not os.path.isfile(os.path.join(dir, file)):
134                        shutil.copy(file_path, dir)
135
136    return dir
137
138
139class ReportProblem:
140    """
141    Class to check for problems with specific values
142    """
143    def __nonzero__(self):
144        type, value, tb = sys.exc_info()
145        if type is not None and issubclass(type, py_compile.PyCompileError):
146            print("Problem with", repr(value))
147            raise type, value, tb
148        return 1
149
150report_problem = ReportProblem()
151
152
153def compile_file(dir):
154    """
155    Compile a py file
156    """
157    try:
158        import compileall
159        compileall.compile_dir(dir=dir, ddir=dir, force=0,
160                               quiet=report_problem)
161    except:
162        return sys.exc_info()[1]
163    return None
164
165
166def _find_models():
167    """
168    Find custom models
169    """
170    # List of plugin objects
171    directory = find_plugins_dir()
172    # Go through files in plug-in directory
173    if not os.path.isdir(directory):
174        msg = "SasView couldn't locate Model plugin folder %r." % directory
175        logger.warning(msg)
176        return {}
177
178    plugin_log("looking for models in: %s" % str(directory))
179    # compile_file(directory)  #always recompile the folder plugin
180    logger.info("plugin model dir: %s" % str(directory))
181
182    plugins = {}
183    for filename in os.listdir(directory):
184        name, ext = os.path.splitext(filename)
185        if ext == '.py' and not name == '__init__':
186            path = os.path.abspath(os.path.join(directory, filename))
187            try:
188                model = load_custom_model(path)
189                if not model.name.count(PLUGIN_NAME_BASE):
190                    model.name = PLUGIN_NAME_BASE + model.name
191                plugins[model.name] = model
192            except Exception:
193                msg = traceback.format_exc()
194                msg += "\nwhile accessing model in %r" % path
195                plugin_log(msg)
196                logger.warning("Failed to load plugin %r. See %s for details"
197                               % (path, PLUGIN_LOG))
198
199    return plugins
200
201
202class ModelList(object):
203    """
204    Contains dictionary of model and their type
205    """
206    def __init__(self):
207        """
208        """
209        self.mydict = {}
210
211    def set_list(self, name, mylist):
212        """
213        :param name: the type of the list
214        :param mylist: the list to add
215
216        """
217        if name not in self.mydict.keys():
218            self.reset_list(name, mylist)
219
220    def reset_list(self, name, mylist):
221        """
222        :param name: the type of the list
223        :param mylist: the list to add
224        """
225        self.mydict[name] = mylist
226
227    def get_list(self):
228        """
229        return all the list stored in a dictionary object
230        """
231        return self.mydict
232
233
234class ModelManagerBase:
235    """
236    Base class for the model manager
237    """
238    ## external dict for models
239    model_combobox = ModelList()
240    ## Dictionary of form factor models
241    form_factor_dict = {}
242    ## dictionary of structure factor models
243    struct_factor_dict = {}
244    ##list of structure factors
245    struct_list = []
246    ##list of model allowing multiplication by a structure factor
247    multiplication_factor = []
248    ##list of multifunctional shapes (i.e. that have user defined number of levels
249    multi_func_list = []
250    ## list of added models -- currently python models found in the plugin dir.
251    plugins = []
252    ## Event owner (guiframe)
253    event_owner = None
254    last_time_dir_modified = 0
255
256    def __init__(self):
257        self.model_dictionary = {}
258        self.stored_plugins = {}
259        self._getModelList()
260
261    def findModels(self):
262        """
263        find  plugin model in directory of plugin .recompile all file
264        in the directory if file were modified
265        """
266        temp = {}
267        if self.is_changed():
268            return  _find_models()
269        logger.info("plugin model : %s" % str(temp))
270        return temp
271
272    def _getModelList(self):
273        """
274        List of models we want to make available by default
275        for this application
276
277        :return: the next free event ID following the new menu events
278
279        """
280
281        # Regular model names only
282        self.model_name_list = []
283
284        # Build list automagically from sasmodels package
285        for model in load_standard_models():
286            self.model_dictionary[model.name] = model
287            if model.is_structure_factor:
288                self.struct_list.append(model)
289            if model.is_form_factor:
290                self.multiplication_factor.append(model)
291            if model.is_multiplicity_model:
292                self.multi_func_list.append(model)
293            else:
294                self.model_name_list.append(model.name)
295
296        # Looking for plugins
297        self.stored_plugins = self.findModels()
298        self.plugins = self.stored_plugins.values()
299        for name, plug in self.stored_plugins.iteritems():
300            self.model_dictionary[name] = plug
301            # TODO: Remove 'hasattr' statements when old style plugin models
302            # are no longer supported. All sasmodels models will have
303            # the required attributes.
304            if hasattr(plug, 'is_structure_factor') and plug.is_structure_factor:
305                self.struct_list.append(plug)
306                self.plugins.remove(plug)
307            elif hasattr(plug, 'is_form_factor') and plug.is_form_factor:
308                self.multiplication_factor.append(plug)
309            if hasattr(plug, 'is_multiplicity_model') and plug.is_multiplicity_model:
310                self.multi_func_list.append(plug)
311
312        self._get_multifunc_models()
313
314        return 0
315
316    def is_changed(self):
317        """
318        check the last time the plugin dir has changed and return true
319        is the directory was modified else return false
320        """
321        is_modified = False
322        plugin_dir = find_plugins_dir()
323        if os.path.isdir(plugin_dir):
324            temp = os.path.getmtime(plugin_dir)
325            if  self.last_time_dir_modified != temp:
326                is_modified = True
327                self.last_time_dir_modified = temp
328
329        return is_modified
330
331    def update(self):
332        """
333        return a dictionary of model if
334        new models were added else return empty dictionary
335        """
336        new_plugins = self.findModels()
337        if len(new_plugins) > 0:
338            for name, plug in  new_plugins.iteritems():
339                if name not in self.stored_plugins.keys():
340                    self.stored_plugins[name] = plug
341                    self.plugins.append(plug)
342                    self.model_dictionary[name] = plug
343            self.model_combobox.set_list("Plugin Models", self.plugins)
344            return self.model_combobox.get_list()
345        else:
346            return {}
347
348    def plugins_reset(self):
349        """
350        return a dictionary of model
351        """
352        self.plugins = []
353        self.stored_plugins = _find_models()
354        structure_names = [model.name for model in self.struct_list]
355        form_names = [model.name for model in self.multiplication_factor]
356
357        # Remove all plugin structure factors and form factors
358        for name in copy(structure_names):
359            if '[plug-in]' in name:
360                i = structure_names.index(name)
361                del self.struct_list[i]
362                structure_names.remove(name)
363        for name in copy(form_names):
364            if '[plug-in]' in name:
365                i = form_names.index(name)
366                del self.multiplication_factor[i]
367                form_names.remove(name)
368
369        # Add new plugin structure factors and form factors
370        for name, plug in self.stored_plugins.iteritems():
371            if plug.is_structure_factor:
372                if name in structure_names:
373                    # Delete the old model from self.struct list
374                    i = structure_names.index(name)
375                    del self.struct_list[i]
376                # Add the new model to self.struct_list
377                self.struct_list.append(plug)
378            elif plug.is_form_factor:
379                if name in form_names:
380                    # Delete the old model from self.multiplication_factor
381                    i = form_names.index(name)
382                    del self.multiplication_factor[i]
383                # Add the new model to self.multiplication_factor
384                self.multiplication_factor.append(plug)
385
386            # Add references to the updated model
387            self.stored_plugins[name] = plug
388            if not plug.is_structure_factor:
389                # Don't show S(Q) models in the 'Plugin Models' dropdown
390                self.plugins.append(plug)
391            self.model_dictionary[name] = plug
392
393        self.model_combobox.reset_list("Plugin Models", self.plugins)
394        self.model_combobox.reset_list("Structure Factors", self.struct_list)
395        self.model_combobox.reset_list("P(Q)*S(Q)", self.multiplication_factor)
396
397        return self.model_combobox.get_list()
398
399    def _on_model(self, evt):
400        """
401        React to a model menu event
402
403        :param event: wx menu event
404
405        """
406        if int(evt.GetId()) in self.form_factor_dict.keys():
407            from sasmodels.sasview_model import MultiplicationModel
408            self.model_dictionary[MultiplicationModel.__name__] = MultiplicationModel
409            model1, model2 = self.form_factor_dict[int(evt.GetId())]
410            model = MultiplicationModel(model1, model2)
411        else:
412            model = self.struct_factor_dict[str(evt.GetId())]()
413
414
415    def _get_multifunc_models(self):
416        """
417        Get the multifunctional models
418        """
419        items = [item for item in self.plugins if item.is_multiplicity_model]
420        self.multi_func_list = items
421
422    def get_model_list(self):
423        """
424        return dictionary of models for fitpanel use
425
426        """
427        ## Model_list now only contains attribute lists not category list.
428        ## Eventually this should be in one master list -- read in category
429        ## list then pull those models that exist and get attributes then add
430        ## to list ..and if model does not exist remove from list as now
431        ## and update json file.
432        ##
433        ## -PDB   April 26, 2014
434
435#        self.model_combobox.set_list("Shapes", self.shape_list)
436#        self.model_combobox.set_list("Shape-Independent",
437#                                     self.shape_indep_list)
438        self.model_combobox.set_list("Structure Factors", self.struct_list)
439        self.model_combobox.set_list("Plugin Models", self.plugins)
440        self.model_combobox.set_list("P(Q)*S(Q)", self.multiplication_factor)
441        self.model_combobox.set_list("multiplication",
442                                     self.multiplication_factor)
443        self.model_combobox.set_list("Multi-Functions", self.multi_func_list)
444        return self.model_combobox.get_list()
445
446    def get_model_name_list(self):
447        """
448        return regular model name list
449        """
450        return self.model_name_list
451
452    def get_model_dictionary(self):
453        """
454        return dictionary linking model names to objects
455        """
456        return self.model_dictionary
457
458
459class ModelManager(object):
460    """
461    implement model
462    """
463    __modelmanager = ModelManagerBase()
464    cat_model_list = [__modelmanager.model_dictionary[model_name] for model_name \
465                      in __modelmanager.model_dictionary.keys() \
466                      if model_name not in __modelmanager.stored_plugins.keys()]
467
468    CategoryInstaller.check_install(model_list=cat_model_list)
469    def findModels(self):
470        return self.__modelmanager.findModels()
471
472    def _getModelList(self):
473        return self.__modelmanager._getModelList()
474
475    def is_changed(self):
476        return self.__modelmanager.is_changed()
477
478    def update(self):
479        return self.__modelmanager.update()
480
481    def plugins_reset(self):
482        return self.__modelmanager.plugins_reset()
483
484    def populate_menu(self, modelmenu, event_owner):
485        return self.__modelmanager.populate_menu(modelmenu, event_owner)
486
487    def _on_model(self, evt):
488        return self.__modelmanager._on_model(evt)
489
490    def _get_multifunc_models(self):
491        return self.__modelmanager._get_multifunc_models()
492
493    def get_model_list(self):
494        return self.__modelmanager.get_model_list()
495
496    def get_model_name_list(self):
497        return self.__modelmanager.get_model_name_list()
498
499    def get_model_dictionary(self):
500        return self.__modelmanager.get_model_dictionary()
Note: See TracBrowser for help on using the repository browser.