Gentoo Archives: gentoo-portage-dev

From: Brian Harring <ferringb@g.o>
To: gentoo-portage-dev@l.g.o
Subject: Re: [gentoo-portage-dev] Plugin framework
Date: Sun, 13 Nov 2005 02:57:39
Message-Id: 20051113025716.GC17300@nightcrawler
In Reply to: [gentoo-portage-dev] Plugin framework by Jason Stubbs
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

Replies

Subject Author
Re: [gentoo-portage-dev] Plugin framework Jason Stubbs <jstubbs@g.o>
Re: [gentoo-portage-dev] Plugin framework Jason Stubbs <jstubbs@g.o>