1 |
On Sunday 13 November 2005 11:57, Brian Harring wrote: |
2 |
> On Sat, Nov 12, 2005 at 11:33:06PM +0900, Jason Stubbs wrote: |
3 |
> > I've had a go at creating a generic plugin framework for portage. The |
4 |
> > attached patch contains: |
5 |
> > |
6 |
> > * plugins/__init__.py that does plugin searching and loading. |
7 |
> > * plugins/cache/__init__.py which specifies what class cache plugins must |
8 |
> > derive from. |
9 |
> > * cache/{anydbm,flat_hash,flat_list,metadata,sqlite}.py copied to |
10 |
> > plugins/cache/ with imports adjusted and classes renamed to match their |
11 |
> > filenames. |
12 |
> > * portage.py edits to the config class to make use of the framework. |
13 |
> > |
14 |
> > Essentially, plugins.registry gives dict access to modules under |
15 |
> > plugins/. To give an example, the plugins.cache.anydbm.anydbm class can |
16 |
> > be accessed via plugins.repository["cache"]["anydbm"]. |
17 |
> |
18 |
> Offhand... you're duplicating pythons "first found" for module |
19 |
> namespace, which I'm not particularly much for, at least for a |
20 |
> registry framework. |
21 |
> |
22 |
> If you're going to introduce a plugin registry, allow the plugins to |
23 |
> exist wherever they want in python namespace, and have the registry |
24 |
> entry point to it (preferably lifting code from 3.0 where possible |
25 |
> also). |
26 |
|
27 |
I'm not familiar with this. If it works better than what I've got now without |
28 |
having to hardcode what plugins are available, I'm all for it. Perhaps |
29 |
registry is the wrong word for what I was trying to do... |
30 |
|
31 |
> > In loading, I'm iterating through sys.path and chopping that out from the |
32 |
> > detected module's path. I didn't need to do this when I was first testing |
33 |
> > the module loader, however. After integrating it with portage I was |
34 |
> > getting "module not found" errors. If anybody knows why that is, I'll get |
35 |
> > rid of that iteration. To be clear, the following doesn't work only when |
36 |
> > running from portage (regardless of the path used): |
37 |
> > |
38 |
> > python -c '__import__("/usr/lib/portage/pym/cache")' |
39 |
> |
40 |
> Err... it's working for me. :) |
41 |
> |
42 |
> Missing __init__.py in cache, or... |
43 |
|
44 |
The python call works but putting that line in _plugin_loader.__init__() |
45 |
failed. |
46 |
|
47 |
> Meanwhile, couple of code comments, then final comments... |
48 |
> |
49 |
> > diff -uNr portage-orig/pym/plugins/__init__.py |
50 |
> > portage-plugin-framework/pym/plugins/__init__.py --- |
51 |
> > portage-orig/pym/plugins/__init__.py 1970-01-01 09:00:00.000000000 +0900 |
52 |
> > +++ portage-plugin-framework/pym/plugins/__init__.py 2005-11-12 |
53 |
> > 21:52:15.000000000 +0900 @@ -0,0 +1,80 @@ |
54 |
> > +class registry: |
55 |
> > + |
56 |
> > + class _plugin_loader: |
57 |
> |
58 |
> Shoving the _pluging_loader class into the registry class namespace |
59 |
|
60 |
Strange obsession with keeping the namespace empty. |
61 |
|
62 |
> makes it a bit hard for derivatives of registry to be implemented. |
63 |
|
64 |
I can't see any possible reason to want to derive from it. The point of it is |
65 |
that pulling external code in should be seamless. If it doesn't serve as is, |
66 |
I'd prefer that it be fixed rather than putting the functionality somewhere |
67 |
else. |
68 |
|
69 |
> > + |
70 |
> > + def __init__(self, path): |
71 |
> > + try: |
72 |
> > + import sys |
73 |
> > + for prefix in sys.path: |
74 |
> > + if path.startswith(prefix): |
75 |
> |
76 |
> *cough* |
77 |
> python -c'import sys;print "" in sys.path' |
78 |
> |
79 |
> That won't do what you're after... |
80 |
> Questionable how well it's going to play with non-normalized paths |
81 |
> also. |
82 |
|
83 |
So that's why it was working from the command line! Actually, it is doing |
84 |
exactly what I was after. Path itself comes from __file__ which comes from a |
85 |
path that was originally derived from sys.path. |
86 |
|
87 |
> > + mod_name = path[len(prefix)+1:] |
88 |
> > + self._plugin = __import__(mod_name) |
89 |
> > + getattr(self._plugin, "plugin_class") |
90 |
> > + break |
91 |
> > + except (ImportError, AttributeError), e: |
92 |
> > + raise ImportError |
93 |
> |
94 |
> This leaves partially imported modules in sys.modules on import |
95 |
> failures; if doing plugins, would suggest raiding portage.util.modules |
96 |
> from 3.0 (already addressed it there). |
97 |
|
98 |
Yep, I was aware of that... Didn't want to make the code too messy when the |
99 |
interface itself could be thrown out. |
100 |
|
101 |
> > + self._path = path |
102 |
> > + self._loaded = {} |
103 |
> > + |
104 |
> > + def __getitem__(self, name): |
105 |
> |
106 |
> This is not thread safe (addressed in 3.0) |
107 |
|
108 |
This is an interesting point. I wasn't thinking about thread-safety at all, |
109 |
but when should I be? Should everything be implemented with thread safety in |
110 |
mind? If not, at what point should the line be drawn? |
111 |
|
112 |
> > + if name in self._loaded: |
113 |
> > + return self._loaded[name] |
114 |
> > + try: |
115 |
> > + import os |
116 |
> > + plugin_path = os.path.join(self._path, name) |
117 |
> > + import sys |
118 |
> > + for prefix in sys.path: |
119 |
> > + if plugin_path.startswith(prefix): |
120 |
> > + plugin_name = plugin_path[len(prefix)+1:] |
121 |
> > + plugin = __import__(plugin_name) |
122 |
> > + break |
123 |
> > + plugin = getattr(plugin, name) |
124 |
> > + if issubclass(plugin, self._plugin.plugin_class): |
125 |
> > + self._loaded[name] = plugin |
126 |
> > + return plugin |
127 |
> > + except (ImportError, AttributeError), e: |
128 |
> > + pass |
129 |
> |
130 |
> Same thing here; plus it's redundant code, abuse a method... |
131 |
|
132 |
Yep, it was much cleaner when I wrote it. Then after trying it, it didn't work |
133 |
so I incrementally modified until it did. I sent what was working before |
134 |
heading to bed. ;) |
135 |
|
136 |
> > + raise KeyError(name) |
137 |
> > + |
138 |
> > + def keys(self): |
139 |
> > + import os |
140 |
> > + for name in os.listdir(self._path): |
141 |
> > + (name, ext) = os.path.splitext(name) |
142 |
> > + if name.startswith("_") or not ext.startswith(".py"): |
143 |
> > + continue |
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 |
> > + return keys |
152 |
> > + |
153 |
> > + def __init__(self): |
154 |
> > + import os |
155 |
> |
156 |
> import globally (you're using os in multiple methods) |
157 |
|
158 |
Again, the weird empty namespace obsession. |
159 |
|
160 |
$ python -c 'import plugins; print dir(plugins)' |
161 |
['__builtins__', '__doc__', '__file__', '__name__', '__path__', 'registry'] |
162 |
|
163 |
There's something attractive about the above, but I'm not married to it. |
164 |
|
165 |
> > + self._path = os.path.dirname(__file__) |
166 |
> > + self._loaded = {} |
167 |
> > + |
168 |
> > + def __getitem__(self, name): |
169 |
> > + if name not in self._loaded: |
170 |
> > + try: |
171 |
> > + import os |
172 |
> > + self._loaded[name] = self._plugin_loader(os.path.join(self._path, |
173 |
> > name)) + except ImportError, e: |
174 |
> > + raise KeyError, name |
175 |
> > + return self._loaded[name] |
176 |
> > + |
177 |
> > + def keys(self): |
178 |
> > + import os |
179 |
> > + for name in os.listdir(self._path): |
180 |
> > + if name not in self._loaded: |
181 |
> > + try: |
182 |
> > + self[name] |
183 |
> > + except KeyError: |
184 |
> > + pass |
185 |
> > + keys = self._loaded.keys() |
186 |
> > + keys.sort() |
187 |
> |
188 |
> Why the sort? |
189 |
|
190 |
Yep. Shouldn't be there. It comes from the sleepy misguided thought that the |
191 |
method would only be used by some user interface allowing the user to select |
192 |
what plugin they wanted to use... |
193 |
|
194 |
> > + return keys |
195 |
> > + |
196 |
> > +registry = registry() |
197 |
> |
198 |
> <snip cache and other stuff> |
199 |
> |
200 |
> > diff -uNr portage-orig/pym/portage.py |
201 |
> > portage-plugin-framework/pym/portage.py --- |
202 |
> > portage-orig/pym/portage.py 2005-11-12 21:53:05.000000000 +0900 +++ |
203 |
> > portage-plugin-framework/pym/portage.py 2005-11-12 21:52:15.000000000 |
204 |
> > +0900 @@ -1229,8 +1210,18 @@ |
205 |
> > self.virtuals = self.getvirtuals(root) |
206 |
> > |
207 |
> > def load_best_module(self,property_string): |
208 |
> > - best_mod = |
209 |
> > best_from_dict(property_string,self.modules,self.module_priority) |
210 |
> > - return load_mod(best_mod) |
211 |
> > + for prio in self.module_priority: |
212 |
> > + if property_string not in self.modules[prio]: |
213 |
> > + continue |
214 |
> > + mod_split = self.modules[prio][property_string].split(".", 1) |
215 |
> > + if len(mod_split) == 2: |
216 |
> > + (mod_type, mod_name) = mod_split |
217 |
> > + #try: |
218 |
> > + return plugins.registry[mod_type][mod_name] |
219 |
> > + #except KeyError: |
220 |
> > + # pass |
221 |
> > + writemsg("%s module %s could not be loaded.\n" % (property_string, |
222 |
> > self.modules[prio][property_string])) + raise KeyError(property_string) |
223 |
> > |
224 |
> > def lock(self): |
225 |
> > self.locked = 1 |
226 |
> |
227 |
> config.module_priority is _evil_ (yep, first I've noticed that gem). |
228 |
> If a user specified cache backend can't be loaded, falling to a |
229 |
> default is a great way to piss users off (that's a bad 'helpful' |
230 |
> feature). Meanwhile, back to your patch... |
231 |
|
232 |
Actually, that was also my mistaken addition. The original code uses |
233 |
module_priority but only tries loading the first found. More |
234 |
misguidedness. :( |
235 |
|
236 |
> Offhand... what's the non-cache intention of this? I'm not seeing the |
237 |
> gain for stable aside from a more complex handling of cache plugins, |
238 |
> and allowing for python to lose it's marbles in importing- case in |
239 |
> point, while you're trying to do lookups in each sys.path for a plugin |
240 |
> namespace, you're still going to have a l->r namespace pollution. |
241 |
|
242 |
So, as I said before, the point is to unify plugin management. Instead of |
243 |
having the imports done wherever something can be plugged in, unify that and |
244 |
do it all in one place. The cache and elog plugin selection(s) come from user |
245 |
settings but emaint (and repoman whenever that happens (and possibly even |
246 |
emerge itself one day?)) needs to automatically detect what's available and |
247 |
use it. |
248 |
|
249 |
-- |
250 |
Jason Stubbs |
251 |
|
252 |
|
253 |
-- |
254 |
gentoo-portage-dev@g.o mailing list |