ClassRegistry.py 6.65 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
#MDANSE : Molecular Dynamics Analysis for Neutron Scattering Experiments
#------------------------------------------------------------------------------------------
#Copyright (C)
#2015- Eric C. Pellegrini Institut Laue-Langevin
#BP 156
#6, rue Jules Horowitz
#38042 Grenoble Cedex 9
#France
#pellegrini[at]ill.fr
#goret[at]ill.fr
#aoun[at]ill.fr
#
#This library is free software; you can redistribute it and/or
#modify it under the terms of the GNU Lesser General Public
#License as published by the Free Software Foundation; either
#version 2.1 of the License, or (at your option) any later version.
#
#This library is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#Lesser General Public License for more details.
#
#You should have received a copy of the GNU Lesser General Public
#License along with this library; if not, write to the Free Software
#Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

''' 
Created on Mar 30, 2015

30
:author: Eric C. Pellegrini
31 32
'''

33
import glob
eric pellegrini's avatar
eric pellegrini committed
34
import imp
35 36
import inspect
import os
eric pellegrini's avatar
test  
eric pellegrini committed
37

38
from MDANSE.Core.Singleton import Singleton
eric pellegrini's avatar
test  
eric pellegrini committed
39
    
40 41 42 43 44
def path_to_module(path,stop=""):
    
    path, _ = os.path.splitext(path)
    
    splittedPath = path.split(os.sep)
45
        
46
    try:
47
        idx = splittedPath[::-1].index(stop)
48 49 50
    except ValueError:
        idx = 0
    finally:
51
        module = ".".join(splittedPath[len(splittedPath)-1-idx:])
52 53 54
            
    return module
        
55
class ClassRegistry(object):
eric pellegrini's avatar
test  
eric pellegrini committed
56
    '''
57
    Metaclass that registers the classes that make the MDANSE framework.
eric pellegrini's avatar
test  
eric pellegrini committed
58

59 60
    The MDANSE framework is based on a set of interfaces that covers different aspects of the framework such 
    as the analysis, the input data, the output file formats ... By metaclassing each base class of these interfaces 
eric pellegrini's avatar
eric pellegrini committed
61 62
    with :py:class:`~MDANSE.Core.ClassRegistry.ClassRegistry` object, their respective concrete class instances will be 
    automatically registered at import time in a data structure that can be further used all over the framework.
63 64 65
        
    The data structure used to store the concrete classes is a nested dictionary whose primary key 
    is the :py:attr:`type` class attribute of the base class they are inheriting from and secondary key is 
66 67
    their own :py:attr:`type` class attribute. Any concrete class of those interfaces that does not define the :py:attr:`type` 
    class attribute will not be registered.    
eric pellegrini's avatar
test  
eric pellegrini committed
68 69
    '''
    
70
    __metaclass__ = Singleton
eric pellegrini's avatar
test  
eric pellegrini committed
71

72 73 74 75 76 77 78
    def __init__(self):
        
        self._registry = {}
        
        self._sections = {}
        
    def __setitem__(self,name,cls):
eric pellegrini's avatar
test  
eric pellegrini committed
79

80 81 82 83
        # The class to be registered must have a class attribute "_registry" to be registered, otherwise return       
        clsRegistry = getattr(cls,"_registry")
        if clsRegistry is None:
            return
eric pellegrini's avatar
test  
eric pellegrini committed
84
        
85 86 87 88 89 90 91 92 93
        # And this attribute must be a string for the class to be registerable otherwise return
        if not isinstance(clsRegistry,basestring):
            return
        
        # Fetch the branch of the registry corresponding the one of the class to be registred, otherwise create a new branch        
        d = self._registry.setdefault(clsRegistry,{})
        
        # If a class has already been registered with that name return
        if d.has_key(name):
eric pellegrini's avatar
test  
eric pellegrini committed
94 95
            return

96 97 98 99 100 101 102 103
        setattr(cls,"_type",name)
        
        d[name] = cls
                
    def __getitem__(self,name):
        
        return self._registry.get(name,{})

104
    def update(self,packageDir, macros=False):
105 106 107
        '''
        Update the classes registry by importing all the modules contained in a given package.
        
eric pellegrini's avatar
eric pellegrini committed
108
        Only the classes metaclassed by :py:class:`~MDANSE.Core.ClassRegistry.ClassRegistry` will be registered.
109 110 111 112
        
        :param packageDir: the package for which all modules should be imported
        :type packageDir: str
        '''
113
                        
114
        for module in glob.glob(os.path.join(packageDir,'*.py')):
115
            
116 117 118 119
            moduleDir, moduleFile = os.path.split(module)
     
            if moduleFile == '__init__.py':
                continue
120

121 122
            # Any error that may occur here has to be caught. In such case the module is skipped.    
            try:
123 124 125 126 127 128 129 130 131
                if macros:
                    moduleName,_ = os.path.splitext(moduleFile)
                    filehandler,path,description = imp.find_module(moduleName, [moduleDir])
                    imp.load_module(moduleName,filehandler,path,description)
                    filehandler.close()
                else:
                    moduleName, _ = os.path.splitext(moduleFile)
                    module = path_to_module(module,stop="MDANSE")
                    __import__(module)
132 133
            except:
                continue
134
        
135
    def info(self, interface):
eric pellegrini's avatar
test  
eric pellegrini committed
136
        '''
137
        Returns informations about the subclasses of a given base class stored in the registry.
eric pellegrini's avatar
test  
eric pellegrini committed
138 139
        
        :param interface: the name of base class of whom information about its subclasses is requested
140
        :type interface: str
141 142 143
        
        :return: return the stringified list of subclasses of a given registered interface.
        :rtype: str
eric pellegrini's avatar
test  
eric pellegrini committed
144 145
        '''
                
146
        if not self._registry.has_key(interface):
eric pellegrini's avatar
test  
eric pellegrini committed
147 148
            return "The interface " + interface + " is not registered"

149
        words = ["Name", "Class","File"]
eric pellegrini's avatar
test  
eric pellegrini committed
150

151 152 153 154 155 156 157
        contents = []
        contents.append("="*130)
        contents.append("{1:{0}s} {2:50s} {3}")
        contents.append("="*130)
        
        maxlength = -1
        
eric pellegrini's avatar
test  
eric pellegrini committed
158
        # Loop over the registry items.
159
        for i, (k, v) in enumerate(sorted(self._registry[interface].items())):
eric pellegrini's avatar
test  
eric pellegrini committed
160 161 162 163
            
            # Get the module corresponding to the job class.
            mod = inspect.getmodule(v)

164 165 166 167 168 169 170
            words.extend([k, v.__name__,mod.__file__])

            contents.append("{%d:{0}s} {%d:50} {%d}" % (3*i+4,3*i+5,3*i+6))
            
            maxlength = max(len(k),maxlength)
                        
        contents.append('-' * 130)
eric pellegrini's avatar
test  
eric pellegrini committed
171
        
172 173 174
        contents = "\n".join(contents)        
                    
        return contents.format(maxlength,*words)
eric pellegrini's avatar
test  
eric pellegrini committed
175

176 177
    @property
    def interfaces(self):
178 179 180
        '''
        Returns the interfaces that are currently registered.
        
181 182
        :return: the interfaces currently registered.
        :rtype: list of str
183 184
        '''
        
185 186 187 188 189
        return sorted(self._registry.keys())
        
REGISTRY = ClassRegistry()