1 |
On Sat, Nov 12, 2005 at 11:33:06PM +0900, Jason Stubbs wrote: |
2 |
> I've had a go at creating a generic plugin framework for portage. The attached |
3 |
> patch contains: |
4 |
> |
5 |
> * plugins/__init__.py that does plugin searching and loading. |
6 |
> * plugins/cache/__init__.py which specifies what class cache plugins must |
7 |
> derive from. |
8 |
> * cache/{anydbm,flat_hash,flat_list,metadata,sqlite}.py copied to |
9 |
> plugins/cache/ with imports adjusted and classes renamed to match their |
10 |
> filenames. |
11 |
> * portage.py edits to the config class to make use of the framework. |
12 |
> |
13 |
> Essentially, plugins.registry gives dict access to modules under plugins/. To |
14 |
> give an example, the plugins.cache.anydbm.anydbm class can be accessed via |
15 |
> plugins.repository["cache"]["anydbm"]. |
16 |
|
17 |
Offhand... you're duplicating pythons "first found" for module |
18 |
namespace, which I'm not particularly much for, at least for a |
19 |
registry framework. |
20 |
|
21 |
If you're going to introduce a plugin registry, allow the plugins to |
22 |
exist wherever they want in python namespace, and have the registry |
23 |
entry point to it (preferably lifting code from 3.0 where possible |
24 |
also). |
25 |
|
26 |
|
27 |
> In loading, I'm iterating through sys.path and chopping that out from the |
28 |
> detected module's path. I didn't need to do this when I was first testing the |
29 |
> module loader, however. After integrating it with portage I was getting |
30 |
> "module not found" errors. If anybody knows why that is, I'll get rid of that |
31 |
> iteration. To be clear, the following doesn't work only when running from |
32 |
> portage (regardless of the path used): |
33 |
> |
34 |
> python -c '__import__("/usr/lib/portage/pym/cache")' |
35 |
|
36 |
Err... it's working for me. :) |
37 |
|
38 |
Missing __init__.py in cache, or... |
39 |
|
40 |
Meanwhile, couple of code comments, then final comments... |
41 |
|
42 |
|
43 |
> diff -uNr portage-orig/pym/plugins/__init__.py portage-plugin-framework/pym/plugins/__init__.py |
44 |
> --- portage-orig/pym/plugins/__init__.py 1970-01-01 09:00:00.000000000 +0900 |
45 |
> +++ portage-plugin-framework/pym/plugins/__init__.py 2005-11-12 21:52:15.000000000 +0900 |
46 |
> @@ -0,0 +1,80 @@ |
47 |
> +class registry: |
48 |
> + |
49 |
> + class _plugin_loader: |
50 |
|
51 |
Shoving the _pluging_loader class into the registry class namespace |
52 |
makes it a bit hard for derivatives of registry to be implemented. |
53 |
|
54 |
|
55 |
> + |
56 |
> + def __init__(self, path): |
57 |
> + try: |
58 |
> + import sys |
59 |
> + for prefix in sys.path: |
60 |
> + if path.startswith(prefix): |
61 |
|
62 |
*cough* |
63 |
python -c'import sys;print "" in sys.path' |
64 |
|
65 |
That won't do what you're after... |
66 |
Questionable how well it's going to play with non-normalized paths |
67 |
also. |
68 |
|
69 |
> + mod_name = path[len(prefix)+1:] |
70 |
> + self._plugin = __import__(mod_name) |
71 |
> + getattr(self._plugin, "plugin_class") |
72 |
> + break |
73 |
> + except (ImportError, AttributeError), e: |
74 |
> + raise ImportError |
75 |
|
76 |
This leaves partially imported modules in sys.modules on import |
77 |
failures; if doing plugins, would suggest raiding portage.util.modules |
78 |
from 3.0 (already addressed it there). |
79 |
|
80 |
> + self._path = path |
81 |
> + self._loaded = {} |
82 |
> + |
83 |
> + def __getitem__(self, name): |
84 |
|
85 |
This is not thread safe (addressed in 3.0) |
86 |
|
87 |
> + if name in self._loaded: |
88 |
> + return self._loaded[name] |
89 |
> + try: |
90 |
> + import os |
91 |
> + plugin_path = os.path.join(self._path, name) |
92 |
> + import sys |
93 |
> + for prefix in sys.path: |
94 |
> + if plugin_path.startswith(prefix): |
95 |
> + plugin_name = plugin_path[len(prefix)+1:] |
96 |
> + plugin = __import__(plugin_name) |
97 |
> + break |
98 |
> + plugin = getattr(plugin, name) |
99 |
> + if issubclass(plugin, self._plugin.plugin_class): |
100 |
> + self._loaded[name] = plugin |
101 |
> + return plugin |
102 |
> + except (ImportError, AttributeError), e: |
103 |
> + pass |
104 |
|
105 |
Same thing here; plus it's redundant code, abuse a method... |
106 |
|
107 |
> + raise KeyError(name) |
108 |
> + |
109 |
> + def keys(self): |
110 |
> + import os |
111 |
> + for name in os.listdir(self._path): |
112 |
> + (name, ext) = os.path.splitext(name) |
113 |
> + if name.startswith("_") or not ext.startswith(".py"): |
114 |
> + continue |
115 |
> + if name not in self._loaded: |
116 |
> + try: |
117 |
> + self[name] |
118 |
> + except KeyError: |
119 |
> + pass |
120 |
> + keys = self._loaded.keys() |
121 |
> + keys.sort() |
122 |
> + return keys |
123 |
> + |
124 |
> + def __init__(self): |
125 |
> + import os |
126 |
|
127 |
import globally (you're using os in multiple methods) |
128 |
|
129 |
> + self._path = os.path.dirname(__file__) |
130 |
> + self._loaded = {} |
131 |
> + |
132 |
> + def __getitem__(self, name): |
133 |
> + if name not in self._loaded: |
134 |
> + try: |
135 |
> + import os |
136 |
> + self._loaded[name] = self._plugin_loader(os.path.join(self._path, name)) |
137 |
> + except ImportError, e: |
138 |
> + raise KeyError, name |
139 |
> + return self._loaded[name] |
140 |
> + |
141 |
> + def keys(self): |
142 |
> + import os |
143 |
> + for name in os.listdir(self._path): |
144 |
> + if name not in self._loaded: |
145 |
> + try: |
146 |
> + self[name] |
147 |
> + except KeyError: |
148 |
> + pass |
149 |
> + keys = self._loaded.keys() |
150 |
> + keys.sort() |
151 |
|
152 |
Why the sort? |
153 |
|
154 |
> + return keys |
155 |
> + |
156 |
> +registry = registry() |
157 |
|
158 |
<snip cache and other stuff> |
159 |
|
160 |
> diff -uNr portage-orig/pym/portage.py portage-plugin-framework/pym/portage.py |
161 |
> --- portage-orig/pym/portage.py 2005-11-12 21:53:05.000000000 +0900 |
162 |
> +++ portage-plugin-framework/pym/portage.py 2005-11-12 21:52:15.000000000 +0900 |
163 |
> @@ -1229,8 +1210,18 @@ |
164 |
> self.virtuals = self.getvirtuals(root) |
165 |
> |
166 |
> def load_best_module(self,property_string): |
167 |
> - best_mod = best_from_dict(property_string,self.modules,self.module_priority) |
168 |
> - return load_mod(best_mod) |
169 |
> + for prio in self.module_priority: |
170 |
> + if property_string not in self.modules[prio]: |
171 |
> + continue |
172 |
> + mod_split = self.modules[prio][property_string].split(".", 1) |
173 |
> + if len(mod_split) == 2: |
174 |
> + (mod_type, mod_name) = mod_split |
175 |
> + #try: |
176 |
> + return plugins.registry[mod_type][mod_name] |
177 |
> + #except KeyError: |
178 |
> + # pass |
179 |
> + writemsg("%s module %s could not be loaded.\n" % (property_string, self.modules[prio][property_string])) |
180 |
> + raise KeyError(property_string) |
181 |
> |
182 |
> def lock(self): |
183 |
> self.locked = 1 |
184 |
|
185 |
config.module_priority is _evil_ (yep, first I've noticed that gem). |
186 |
If a user specified cache backend can't be loaded, falling to a |
187 |
default is a great way to piss users off (that's a bad 'helpful' |
188 |
feature). Meanwhile, back to your patch... |
189 |
|
190 |
Offhand... what's the non-cache intention of this? I'm not seeing the |
191 |
gain for stable aside from a more complex handling of cache plugins, |
192 |
and allowing for python to lose it's marbles in importing- case in |
193 |
point, while you're trying to do lookups in each sys.path for a plugin |
194 |
namespace, you're still going to have a l->r namespace pollution. |
195 |
|
196 |
Python re-uses previously imported modules; cache namespace (fex) is |
197 |
going to default to portage's bundled version, regardless of any |
198 |
modules in different sys.path trying to define their own cache module. |
199 |
Python will just reuse the previous found module... not very flexible. |
200 |
~harring |