Gentoo Archives: gentoo-portage-dev

From: Jason Stubbs <jstubbs@g.o>
To: gentoo-portage-dev@l.g.o
Subject: Re: [gentoo-portage-dev] Plugin framework
Date: Mon, 14 Nov 2005 13:37:45
Message-Id: 200511142238.28175.jstubbs@gentoo.org
In Reply to: Re: [gentoo-portage-dev] Plugin framework by Brian Harring
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

Replies

Subject Author
Re: [gentoo-portage-dev] Plugin framework Marius Mauch <genone@g.o>
Re: [gentoo-portage-dev] Plugin framework Brian Harring <ferringb@g.o>