1
2 """
3
4 @author: Fabio Erculiani <lxnay@sabayonlinux.org>
5 @contact: lxnay@sabayonlinux.org
6 @copyright: Fabio Erculiani
7 @license: GPL-2
8
9 B{Entropy Framework core module}.
10
11 This module contains base classes used by entropy.client,
12 entropy.server and entropy.services.
13
14 "Singleton" is a class that is inherited from singleton objects.
15
16 """
17 import sys
18 import os
19 import inspect
20 from entropy.const import etpConst
21
23
24 """
25 If your class wants to become a sexy Singleton,
26 subclass this and replace __init__ with init_singleton
27 """
28
29 __is_destroyed = False
30 __is_singleton = True
32 instance = cls.__dict__.get("__it__")
33 if instance is not None:
34 if not instance.is_destroyed():
35 return instance
36 cls.__it__ = instance = object.__new__(cls)
37 instance.init_singleton(*args, **kwds)
38 return instance
39
41 """
42 In our world, Singleton instances may be destroyed,
43 this is done by setting a private bool var __is_destroyed
44
45 @rtype: bool
46 @return: instance status, if destroyed or not
47 """
48 return self.__is_destroyed
49
51 """
52 Return if the instance is a singleton
53
54 @rtype: bool
55 @return: class singleton property, if singleton or not
56 """
57 return self.__is_singleton
58
59
61
62 """
63 Generic Entropy Components Plugin Factory (loader).
64 """
65
66 _PLUGIN_SUFFIX = "_plugin"
67 _PYTHON_EXTENSION = ".py"
68
69 - def __init__(self, base_plugin_class, plugin_package_module,
70 default_plugin_name = None, fallback_plugin_name = None):
71 """
72 Entropy Generic Plugin Factory constructor.
73 MANDATORY: every plugin module/package(name) must end with _plugin
74 suffix.
75
76 Base plugin classes must have the following class attributes set:
77
78 - BASE_PLUGIN_API_VERSION: integer describing API revision in use
79 in class
80
81 Subclasses of Base plugin class must have the following class
82 attributes set:
83
84 - PLUGIN_API_VERSION: integer describing the currently implemented
85 plugin API revision, must match with BASE_PLUGIN_API_VERSION
86 above otherwise plugin won't be loaded and a warning will be
87 printed.
88
89 Moreover, plugin classes must be "Python new-style classes", otherwise
90 parser won't be able to determine if classes have subclasses and thus
91 pick the proper object (one with no subclasses!!).
92 See: http://www.python.org/doc/newstyle -- in other words, you have
93 to inherit the built-in "object" class (yeah, it's called object).
94 So, even if using normal classes could work, if you start doing nasty
95 things (nested inherittance of plugin classes), behaviour cannot
96 be guaranteed.
97 If it's not clear, let me repeat once again, valid plugin classes
98 must not have subclasses around! Think about it, it's an obvious thing.
99
100 If plugin class features a "PLUGIN_DISABLED" class attribute with
101 a boolean value of True, such plugin will be ignored.
102
103 @param base_plugin_class: Base EntropyPlugin-based class that valid
104 plugin classes must inherit from.
105 @type base_plugin_class: class
106 @param plugin_package_module: every plugin repository must work as
107 Python package, the value of this argument must be a valid
108 Python package module that can be scanned looking for valid
109 Entropy Plugin classes.
110 @type plugin_package_module: Python module
111 @keyword default_plugin_name: identifier of the default plugin to load
112 @type default_plugin_name: string
113 @keyword fallback_plugin_name: identifier of the fallback plugin to load
114 if default is not available
115 @type fallback_plugin_name: string
116 @raise AttributeError: when passed plugin_package_module is not a
117 valid Python package module
118 """
119 self.__modfile = plugin_package_module.__file__
120 self.__base_class = base_plugin_class
121 self.__plugin_package_module = plugin_package_module
122 self.__default_plugin_name = default_plugin_name
123 self.__fallback_plugin_name = fallback_plugin_name
124 self.__cache = None
125
126
128 """
129 Return currently available EntropyPlugin plugin classes.
130 Note: Entropy plugins can either be Python packages or modules and
131 their name MUST end with PluginFactory._PLUGIN_SUFFIX ("_plugin").
132
133 @return: dictionary composed by Entropy plugin id as key and Entropy
134 Python module as value
135 @rtype: dict
136 """
137 if self.__cache is not None:
138 return self.__cache.copy()
139
140 available = {}
141 base_api = self.__base_class.BASE_PLUGIN_API_VERSION
142
143 pkg_modname = self.__plugin_package_module.__name__
144 for modname in os.listdir(os.path.dirname(self.__modfile)):
145
146 if modname.startswith("__"):
147 continue
148 if not (modname.endswith(EntropyPluginFactory._PYTHON_EXTENSION) \
149 or "." not in modname):
150 continue
151
152 if modname.endswith(EntropyPluginFactory._PYTHON_EXTENSION):
153 modname = modname[:-len(EntropyPluginFactory._PYTHON_EXTENSION)]
154
155 if not modname.endswith(EntropyPluginFactory._PLUGIN_SUFFIX):
156 continue
157
158
159 modname_clean = modname[:-len(EntropyPluginFactory._PLUGIN_SUFFIX)]
160
161 modpath = "%s.%s" % (pkg_modname, modname,)
162
163 try:
164 module = __import__(modpath)
165 except ImportError, err:
166 sys.stderr.write("!!! Entropy Plugin warning, cannot " \
167 "load module: %s | %s !!!\n" % (modpath, err,))
168 continue
169
170 for obj in sys.modules[modpath].__dict__.values():
171
172 if not inspect.isclass(obj):
173 continue
174
175 if not issubclass(obj, self.__base_class):
176 continue
177
178 if hasattr(obj, '__subclasses__'):
179
180 if obj.__subclasses__():
181 continue
182 else:
183 sys.stderr.write("!!! Entropy Plugin warning: " \
184 "%s is not a new style class !!!\n" % (obj,))
185
186 if obj is self.__base_class:
187
188
189 continue
190
191 if not hasattr(obj, "PLUGIN_API_VERSION"):
192 sys.stderr.write("!!! Entropy Plugin warning: " \
193 "no PLUGIN_API_VERSION in %s !!!\n" % (obj,))
194 continue
195
196 if obj.PLUGIN_API_VERSION != base_api:
197 sys.stderr.write("!!! Entropy Plugin warning: " \
198 "PLUGIN_API_VERSION mismatch in %s !!!\n" % (obj,))
199 continue
200
201 if hasattr(obj, 'PLUGIN_DISABLED'):
202 if obj.PLUGIN_DISABLED:
203
204 continue
205
206 available[modname_clean] = obj
207
208 self.__cache = available.copy()
209 return available
210
212 """
213 Return currently configured Entropy Plugin class.
214
215 @return: Entropy plugin class
216 @rtype: entropy.core.EntropyPlugin
217 @raise KeyError: if default plugin is not set or not found
218 """
219 available = self.get_available_plugins()
220 plugin = self.__default_plugin_name
221 fallback = self.__fallback_plugin_name
222 klass = available.get(plugin)
223
224 if klass is None:
225 import warnings
226 warnings.warn("%s: %s" % (
227 "selected Plugin not available, using fallback", plugin,))
228 klass = available.get(fallback)
229
230 if klass is None:
231 raise KeyError
232
233 return klass
234