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()