3 together. The vregistry handles registration of dynamically loaded |
3 together. The vregistry handles registration of dynamically loaded |
4 objects and provides a convenient api to access those objects |
4 objects and provides a convenient api to access those objects |
5 according to a context |
5 according to a context |
6 |
6 |
7 * to interact with the vregistry, objects should inherit from the |
7 * to interact with the vregistry, objects should inherit from the |
8 VObject abstract class |
8 AppObject abstract class |
9 |
9 |
10 * the selection procedure has been generalized by delegating to a |
10 * the selection procedure has been generalized by delegating to a |
11 selector, which is responsible to score the vobject according to the |
11 selector, which is responsible to score the appobject according to the |
12 current state (req, rset, row, col). At the end of the selection, if |
12 current state (req, rset, row, col). At the end of the selection, if |
13 a vobject class has been found, an instance of this class is |
13 a appobject class has been found, an instance of this class is |
14 returned. The selector is instantiated at vobject registration |
14 returned. The selector is instantiated at appobject registration |
15 |
15 |
16 |
16 |
17 :organization: Logilab |
17 :organization: Logilab |
18 :copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. |
18 :copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. |
19 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr |
19 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr |
20 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses |
20 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses |
21 """ |
21 """ |
22 __docformat__ = "restructuredtext en" |
22 __docformat__ = "restructuredtext en" |
23 |
23 |
24 import sys |
24 import sys |
25 import types |
|
26 from os import listdir, stat |
25 from os import listdir, stat |
27 from os.path import dirname, join, realpath, split, isdir, exists |
26 from os.path import dirname, join, realpath, split, isdir, exists |
28 from logging import getLogger |
27 from logging import getLogger |
29 from warnings import warn |
28 from warnings import warn |
30 |
29 |
31 from cubicweb import CW_SOFTWARE_ROOT, set_log_methods |
30 from logilab.common.deprecation import deprecated, class_moved |
|
31 from logilab.common.logging_ext import set_log_methods |
|
32 |
|
33 from cubicweb import CW_SOFTWARE_ROOT |
32 from cubicweb import (RegistryNotFound, ObjectNotFound, NoSelectableObject, |
34 from cubicweb import (RegistryNotFound, ObjectNotFound, NoSelectableObject, |
33 RegistryOutOfDate) |
35 RegistryOutOfDate) |
|
36 from cubicweb.appobject import AppObject |
34 |
37 |
35 # XXX depending on cubicweb.web is ugly, we should deal with uicfg |
38 # XXX depending on cubicweb.web is ugly, we should deal with uicfg |
36 # reset with a good old event / callback system |
39 # reset with a good old event / callback system |
37 try: |
40 try: |
38 from cubicweb.web import uicfg |
41 from cubicweb.web import uicfg |
56 _toload[0][modname] = fileordir |
59 _toload[0][modname] = fileordir |
57 _toload[1].append((fileordir, modname)) |
60 _toload[1].append((fileordir, modname)) |
58 return _toload |
61 return _toload |
59 |
62 |
60 |
63 |
61 class VObject(object): |
64 class Registry(dict): |
62 """visual object, use to be handled somehow by the visual components |
65 |
63 registry. |
66 def __init__(self, config): |
64 |
67 super(Registry, self).__init__() |
65 The following attributes should be set on concret vobject subclasses: |
|
66 |
|
67 :__registry__: |
|
68 name of the registry for this object (string like 'views', |
|
69 'templates'...) |
|
70 :id: |
|
71 object's identifier in the registry (string like 'main', |
|
72 'primary', 'folder_box') |
|
73 :__select__: |
|
74 class'selector |
|
75 |
|
76 Moreover, the `__abstract__` attribute may be set to True to indicate |
|
77 that a vobject is abstract and should not be registered |
|
78 """ |
|
79 # necessary attributes to interact with the registry |
|
80 id = None |
|
81 __registry__ = None |
|
82 __select__ = None |
|
83 |
|
84 @classmethod |
|
85 def registered(cls, registry): |
|
86 """called by the registry when the vobject has been registered. |
|
87 |
|
88 It must return the object that will be actually registered (this |
|
89 may be the right hook to create an instance for example). By |
|
90 default the vobject is returned without any transformation. |
|
91 """ |
|
92 cls.build___select__() |
|
93 return cls |
|
94 |
|
95 @classmethod |
|
96 def selected(cls, *args, **kwargs): |
|
97 """called by the registry when the vobject has been selected. |
|
98 |
|
99 It must return the object that will be actually returned by the |
|
100 .select method (this may be the right hook to create an |
|
101 instance for example). By default the selected object is |
|
102 returned without any transformation. |
|
103 """ |
|
104 return cls |
|
105 |
|
106 @classmethod |
|
107 def classid(cls): |
|
108 """returns a unique identifier for the vobject""" |
|
109 return '%s.%s' % (cls.__module__, cls.__name__) |
|
110 |
|
111 # XXX bw compat code |
|
112 @classmethod |
|
113 def build___select__(cls): |
|
114 for klass in cls.mro(): |
|
115 if klass.__name__ == 'AppRsetObject': |
|
116 continue # the bw compat __selector__ is there |
|
117 klassdict = klass.__dict__ |
|
118 if ('__select__' in klassdict and '__selectors__' in klassdict |
|
119 and '__selgenerated__' not in klassdict): |
|
120 raise TypeError("__select__ and __selectors__ can't be used together on class %s" % cls) |
|
121 if '__selectors__' in klassdict and '__selgenerated__' not in klassdict: |
|
122 cls.__selgenerated__ = True |
|
123 # case where __selectors__ is defined locally (but __select__ |
|
124 # is in a parent class) |
|
125 selectors = klassdict['__selectors__'] |
|
126 if len(selectors) == 1: |
|
127 # micro optimization: don't bother with AndSelector if there's |
|
128 # only one selector |
|
129 select = _instantiate_selector(selectors[0]) |
|
130 else: |
|
131 select = AndSelector(*selectors) |
|
132 cls.__select__ = select |
|
133 |
|
134 |
|
135 class VRegistry(object): |
|
136 """class responsible to register, propose and select the various |
|
137 elements used to build the web interface. Currently, we have templates, |
|
138 views, actions and components. |
|
139 """ |
|
140 |
|
141 def __init__(self, config):#, cache_size=1000): |
|
142 self.config = config |
68 self.config = config |
143 # dictionnary of registry (themself dictionnary) by name |
69 |
144 self._registries = {} |
70 def __getitem__(self, name): |
145 self._lastmodifs = {} |
|
146 |
|
147 def reset(self): |
|
148 self._registries = {} |
|
149 self._lastmodifs = {} |
|
150 if uicfg is not None: |
|
151 reload(uicfg) |
|
152 |
|
153 def __getitem__(self, key): |
|
154 return self._registries[key] |
|
155 |
|
156 def get(self, key, default=None): |
|
157 return self._registries.get(key, default) |
|
158 |
|
159 def items(self): |
|
160 return self._registries.items() |
|
161 |
|
162 def values(self): |
|
163 return self._registries.values() |
|
164 |
|
165 def __contains__(self, key): |
|
166 return key in self._registries |
|
167 |
|
168 def registry(self, name): |
|
169 """return the registry (dictionary of class objects) associated to |
71 """return the registry (dictionary of class objects) associated to |
170 this name |
72 this name |
171 """ |
73 """ |
172 try: |
74 try: |
173 return self._registries[name] |
75 return super(Registry, self).__getitem__(name) |
174 except KeyError: |
76 except KeyError: |
175 raise RegistryNotFound(name), None, sys.exc_info()[-1] |
77 raise ObjectNotFound(name), None, sys.exc_info()[-1] |
176 |
78 |
177 def registry_objects(self, name, oid=None): |
79 def register(self, obj, oid=None, clear=False): |
178 """returns objects registered with the given oid in the given registry. |
80 """base method to add an object in the registry""" |
179 If no oid is given, return all objects in this registry |
81 assert not '__abstract__' in obj.__dict__ |
180 """ |
82 oid = oid or obj.id |
181 registry = self.registry(name) |
83 assert oid |
182 if oid is not None: |
84 if clear: |
183 try: |
85 appobjects = self[oid] = [] |
184 return registry[oid] |
86 else: |
185 except KeyError: |
87 appobjects = self.setdefault(oid, []) |
186 raise ObjectNotFound(oid), None, sys.exc_info()[-1] |
88 # registered() is technically a classmethod but is not declared |
|
89 # as such because we need to compose registered in some cases |
|
90 appobject = obj.registered.im_func(obj, self) |
|
91 assert not appobject in appobjects, \ |
|
92 'object %s is already registered' % appobject |
|
93 assert callable(appobject.__select__), appobject |
|
94 appobjects.append(appobject) |
|
95 |
|
96 def register_and_replace(self, obj, replaced): |
|
97 # XXXFIXME this is a duplication of unregister() |
|
98 # remove register_and_replace in favor of unregister + register |
|
99 # or simplify by calling unregister then register here |
|
100 if hasattr(replaced, 'classid'): |
|
101 replaced = replaced.classid() |
|
102 registered_objs = self.get(obj.id, ()) |
|
103 for index, registered in enumerate(registered_objs): |
|
104 if registered.classid() == replaced: |
|
105 del registered_objs[index] |
|
106 break |
|
107 else: |
|
108 self.warning('trying to replace an unregistered view %s by %s', |
|
109 replaced, obj) |
|
110 self.register(obj) |
|
111 |
|
112 def unregister(self, obj): |
|
113 oid = obj.classid() |
|
114 for registered in self.get(obj.id, ()): |
|
115 # use classid() to compare classes because vreg will probably |
|
116 # have its own version of the class, loaded through execfile |
|
117 if registered.classid() == oid: |
|
118 # XXX automatic reloading management |
|
119 self[obj.id].remove(registered) |
|
120 break |
|
121 else: |
|
122 self.warning('can\'t remove %s, no id %s in the registry', |
|
123 oid, obj.id) |
|
124 |
|
125 def all_objects(self): |
|
126 """return a list containing all objects in this registry. |
|
127 """ |
187 result = [] |
128 result = [] |
188 for objs in registry.values(): |
129 for objs in self.values(): |
189 result += objs |
130 result += objs |
190 return result |
131 return result |
191 |
132 |
192 # dynamic selection methods ################################################ |
133 # dynamic selection methods ################################################ |
193 |
134 |
194 def object_by_id(self, registry, oid, *args, **kwargs): |
135 def object_by_id(self, oid, *args, **kwargs): |
195 """return object in <registry>.<oid> |
136 """return object with the given oid. Only one object is expected to be |
|
137 found. |
196 |
138 |
197 raise `ObjectNotFound` if not object with id <oid> in <registry> |
139 raise `ObjectNotFound` if not object with id <oid> in <registry> |
198 raise `AssertionError` if there is more than one object there |
140 raise `AssertionError` if there is more than one object there |
199 """ |
141 """ |
200 objects = self.registry_objects(registry, oid) |
142 objects = self[oid] |
201 assert len(objects) == 1, objects |
143 assert len(objects) == 1, objects |
202 return objects[0].selected(*args, **kwargs) |
144 return objects[0](*args, **kwargs) |
203 |
145 |
204 def select(self, registry, oid, *args, **kwargs): |
146 def select(self, oid, *args, **kwargs): |
205 """return the most specific object in <registry>.<oid> according to |
147 """return the most specific object among those with the given oid |
206 the given context |
148 according to the given context. |
207 |
149 |
208 raise `ObjectNotFound` if not object with id <oid> in <registry> |
150 raise `ObjectNotFound` if not object with id <oid> in <registry> |
209 raise `NoSelectableObject` if not object apply |
151 raise `NoSelectableObject` if not object apply |
210 """ |
152 """ |
211 return self.select_best(self.registry_objects(registry, oid), |
153 return self.select_best(self[oid], *args, **kwargs) |
212 *args, **kwargs) |
154 |
213 |
155 def select_object(self, oid, *args, **kwargs): |
214 def select_object(self, registry, oid, *args, **kwargs): |
156 """return the most specific object among those with the given oid |
215 """return the most specific object in <registry>.<oid> according to |
157 according to the given context, or None if no object applies. |
216 the given context, or None if no object apply |
158 """ |
217 """ |
159 try: |
218 try: |
160 return self.select(oid, *args, **kwargs) |
219 return self.select(registry, oid, *args, **kwargs) |
|
220 except (NoSelectableObject, ObjectNotFound): |
161 except (NoSelectableObject, ObjectNotFound): |
221 return None |
162 return None |
222 |
163 |
223 def possible_objects(self, registry, *args, **kwargs): |
164 def possible_objects(self, *args, **kwargs): |
224 """return an iterator on possible objects in <registry> for the given |
165 """return an iterator on possible objects in this registry for the given |
225 context |
166 context |
226 """ |
167 """ |
227 for vobjects in self.registry(registry).itervalues(): |
168 for appobjects in self.itervalues(): |
228 try: |
169 try: |
229 yield self.select_best(vobjects, *args, **kwargs) |
170 yield self.select_best(appobjects, *args, **kwargs) |
230 except NoSelectableObject: |
171 except NoSelectableObject: |
231 continue |
172 continue |
232 |
173 |
233 def select_best(self, vobjects, *args, **kwargs): |
174 def select_best(self, appobjects, *args, **kwargs): |
234 """return an instance of the most specific object according |
175 """return an instance of the most specific object according |
235 to parameters |
176 to parameters |
236 |
177 |
237 raise `NoSelectableObject` if not object apply |
178 raise `NoSelectableObject` if not object apply |
238 """ |
179 """ |
239 if len(args) > 1: |
180 if len(args) > 1: |
240 warn('only the request param can not be named when calling select', |
181 warn('only the request param can not be named when calling select', |
241 DeprecationWarning, stacklevel=3) |
182 DeprecationWarning, stacklevel=3) |
242 score, winners = 0, [] |
183 score, winners = 0, [] |
243 for vobject in vobjects: |
184 for appobject in appobjects: |
244 vobjectscore = vobject.__select__(vobject, *args, **kwargs) |
185 appobjectscore = appobject.__select__(appobject, *args, **kwargs) |
245 if vobjectscore > score: |
186 if appobjectscore > score: |
246 score, winners = vobjectscore, [vobject] |
187 score, winners = appobjectscore, [appobject] |
247 elif vobjectscore > 0 and vobjectscore == score: |
188 elif appobjectscore > 0 and appobjectscore == score: |
248 winners.append(vobject) |
189 winners.append(appobject) |
249 if not winners: |
190 if not winners: |
250 raise NoSelectableObject('args: %s\nkwargs: %s %s' |
191 raise NoSelectableObject('args: %s\nkwargs: %s %s' |
251 % (args, kwargs.keys(), |
192 % (args, kwargs.keys(), |
252 [repr(v) for v in vobjects])) |
193 [repr(v) for v in appobjects])) |
253 if len(winners) > 1: |
194 if len(winners) > 1: |
254 if self.config.mode == 'installed': |
195 if self.config.mode == 'installed': |
255 self.error('select ambiguity, args: %s\nkwargs: %s %s', |
196 self.error('select ambiguity, args: %s\nkwargs: %s %s', |
256 args, kwargs.keys(), [repr(v) for v in winners]) |
197 args, kwargs.keys(), [repr(v) for v in winners]) |
257 else: |
198 else: |
258 raise Exception('select ambiguity, args: %s\nkwargs: %s %s' |
199 raise Exception('select ambiguity, args: %s\nkwargs: %s %s' |
259 % (args, kwargs.keys(), |
200 % (args, kwargs.keys(), |
260 [repr(v) for v in winners])) |
201 [repr(v) for v in winners])) |
261 # return the result of the .selected method of the vobject |
202 # return the result of calling the appobject |
262 return winners[0].selected(*args, **kwargs) |
203 return winners[0](*args, **kwargs) |
|
204 |
|
205 |
|
206 class VRegistry(dict): |
|
207 """class responsible to register, propose and select the various |
|
208 elements used to build the web interface. Currently, we have templates, |
|
209 views, actions and components. |
|
210 """ |
|
211 |
|
212 def __init__(self, config): |
|
213 super(VRegistry, self).__init__() |
|
214 self.config = config |
|
215 |
|
216 def reset(self, force_reload=None): |
|
217 self.clear() |
|
218 self._lastmodifs = {} |
|
219 # don't reload uicfg when appobjects modules won't be reloaded as well |
|
220 if uicfg is not None: |
|
221 if force_reload is None: |
|
222 force_reload = self.config.mode == 'dev' |
|
223 if force_reload: |
|
224 reload(uicfg) |
|
225 |
|
226 def __getitem__(self, name): |
|
227 """return the registry (dictionary of class objects) associated to |
|
228 this name |
|
229 """ |
|
230 try: |
|
231 return super(VRegistry, self).__getitem__(name) |
|
232 except KeyError: |
|
233 raise RegistryNotFound(name), None, sys.exc_info()[-1] |
|
234 |
|
235 # dynamic selection methods ################################################ |
|
236 |
|
237 @deprecated('use vreg[registry].object_by_id(oid, *args, **kwargs)') |
|
238 def object_by_id(self, registry, oid, *args, **kwargs): |
|
239 """return object in <registry>.<oid> |
|
240 |
|
241 raise `ObjectNotFound` if not object with id <oid> in <registry> |
|
242 raise `AssertionError` if there is more than one object there |
|
243 """ |
|
244 return self[registry].object_by_id(oid) |
|
245 |
|
246 @deprecated('use vreg[registry].select(oid, *args, **kwargs)') |
|
247 def select(self, registry, oid, *args, **kwargs): |
|
248 """return the most specific object in <registry>.<oid> according to |
|
249 the given context |
|
250 |
|
251 raise `ObjectNotFound` if not object with id <oid> in <registry> |
|
252 raise `NoSelectableObject` if not object apply |
|
253 """ |
|
254 return self[registry].select(oid, *args, **kwargs) |
|
255 |
|
256 @deprecated('use vreg[registry].select_object(oid, *args, **kwargs)') |
|
257 def select_object(self, registry, oid, *args, **kwargs): |
|
258 """return the most specific object in <registry>.<oid> according to |
|
259 the given context, or None if no object apply |
|
260 """ |
|
261 return self[registry].select_object(oid, *args, **kwargs) |
|
262 |
|
263 @deprecated('use vreg[registry].possible_objects(*args, **kwargs)') |
|
264 def possible_objects(self, registry, *args, **kwargs): |
|
265 """return an iterator on possible objects in <registry> for the given |
|
266 context |
|
267 """ |
|
268 return self[registry].possible_objects(*args, **kwargs) |
263 |
269 |
264 # methods for explicit (un)registration ################################### |
270 # methods for explicit (un)registration ################################### |
|
271 |
|
272 # default class, when no specific class set |
|
273 REGISTRY_FACTORY = {None: Registry} |
|
274 |
|
275 def registry_class(self, regid): |
|
276 try: |
|
277 return self.REGISTRY_FACTORY[regid] |
|
278 except KeyError: |
|
279 return self.REGISTRY_FACTORY[None] |
|
280 |
|
281 def setdefault(self, regid): |
|
282 try: |
|
283 return self[regid] |
|
284 except KeyError: |
|
285 self[regid] = self.registry_class(regid)(self.config) |
|
286 return self[regid] |
265 |
287 |
266 # def clear(self, key): |
288 # def clear(self, key): |
267 # regname, oid = key.split('.') |
289 # regname, oid = key.split('.') |
268 # self[regname].pop(oid, None) |
290 # self[regname].pop(oid, None) |
269 |
291 |
280 |
302 |
281 def register(self, obj, registryname=None, oid=None, clear=False): |
303 def register(self, obj, registryname=None, oid=None, clear=False): |
282 """base method to add an object in the registry""" |
304 """base method to add an object in the registry""" |
283 assert not '__abstract__' in obj.__dict__ |
305 assert not '__abstract__' in obj.__dict__ |
284 registryname = registryname or obj.__registry__ |
306 registryname = registryname or obj.__registry__ |
285 oid = oid or obj.id |
307 registry = self.setdefault(registryname) |
286 assert oid |
308 registry.register(obj, oid=oid, clear=clear) |
287 registry = self._registries.setdefault(registryname, {}) |
309 try: |
288 if clear: |
310 vname = obj.__name__ |
289 vobjects = registry[oid] = [] |
|
290 else: |
|
291 vobjects = registry.setdefault(oid, []) |
|
292 # registered() is technically a classmethod but is not declared |
|
293 # as such because we need to compose registered in some cases |
|
294 vobject = obj.registered.im_func(obj, self) |
|
295 assert not vobject in vobjects, \ |
|
296 'object %s is already registered' % vobject |
|
297 assert callable(vobject.__select__), vobject |
|
298 vobjects.append(vobject) |
|
299 try: |
|
300 vname = vobject.__name__ |
|
301 except AttributeError: |
311 except AttributeError: |
302 vname = vobject.__class__.__name__ |
312 vname = obj.__class__.__name__ |
303 self.debug('registered vobject %s in registry %s with id %s', |
313 self.debug('registered appobject %s in registry %s with id %s', |
304 vname, registryname, oid) |
314 vname, registryname, oid) |
305 self._loadedmods[obj.__module__]['%s.%s' % (obj.__module__, oid)] = obj |
315 self._loadedmods[obj.__module__]['%s.%s' % (obj.__module__, oid)] = obj |
306 |
316 |
307 def unregister(self, obj, registryname=None): |
317 def unregister(self, obj, registryname=None): |
308 registryname = registryname or obj.__registry__ |
318 self[registryname or obj.__registry__].unregister(obj) |
309 registry = self.registry(registryname) |
|
310 removed_id = obj.classid() |
|
311 for registered in registry.get(obj.id, ()): |
|
312 # use classid() to compare classes because vreg will probably |
|
313 # have its own version of the class, loaded through execfile |
|
314 if registered.classid() == removed_id: |
|
315 # XXX automatic reloading management |
|
316 registry[obj.id].remove(registered) |
|
317 break |
|
318 else: |
|
319 self.warning('can\'t remove %s, no id %s in the %s registry', |
|
320 removed_id, obj.id, registryname) |
|
321 |
319 |
322 def register_and_replace(self, obj, replaced, registryname=None): |
320 def register_and_replace(self, obj, replaced, registryname=None): |
323 # XXXFIXME this is a duplication of unregister() |
321 self[registryname or obj.__registry__].register_and_replace(obj, replaced) |
324 # remove register_and_replace in favor of unregister + register |
322 |
325 # or simplify by calling unregister then register here |
323 # initialization methods ################################################### |
326 if hasattr(replaced, 'classid'): |
|
327 replaced = replaced.classid() |
|
328 registryname = registryname or obj.__registry__ |
|
329 registry = self.registry(registryname) |
|
330 registered_objs = registry.get(obj.id, ()) |
|
331 for index, registered in enumerate(registered_objs): |
|
332 if registered.classid() == replaced: |
|
333 del registry[obj.id][index] |
|
334 break |
|
335 else: |
|
336 self.warning('trying to replace an unregistered view %s by %s', |
|
337 replaced, obj) |
|
338 self.register(obj, registryname=registryname) |
|
339 |
|
340 # intialization methods ################################################### |
|
341 |
324 |
342 def init_registration(self, path, extrapath=None): |
325 def init_registration(self, path, extrapath=None): |
343 # compute list of all modules that have to be loaded |
326 # compute list of all modules that have to be loaded |
344 self._toloadmods, filemods = _toload_info(path, extrapath) |
327 self._toloadmods, filemods = _toload_info(path, extrapath) |
345 # XXX is _loadedmods still necessary ? It seems like it's useful |
328 # XXX is _loadedmods still necessary ? It seems like it's useful |
458 if '%s.%s' % (regname, cls.id) in self.config['disable-appobjects']: |
441 if '%s.%s' % (regname, cls.id) in self.config['disable-appobjects']: |
459 return |
442 return |
460 self.register(cls) |
443 self.register(cls) |
461 |
444 |
462 # init logging |
445 # init logging |
463 set_log_methods(VObject, getLogger('cubicweb')) |
446 set_log_methods(VRegistry, getLogger('cubicweb.vreg')) |
464 set_log_methods(VRegistry, getLogger('cubicweb.registry')) |
447 set_log_methods(Registry, getLogger('cubicweb.registry')) |
465 |
|
466 |
|
467 # selector base classes and operations ######################################## |
|
468 |
|
469 class Selector(object): |
|
470 """base class for selector classes providing implementation |
|
471 for operators ``&`` and ``|`` |
|
472 |
|
473 This class is only here to give access to binary operators, the |
|
474 selector logic itself should be implemented in the __call__ method |
|
475 |
|
476 |
|
477 a selector is called to help choosing the correct object for a |
|
478 particular context by returning a score (`int`) telling how well |
|
479 the class given as first argument apply to the given context. |
|
480 |
|
481 0 score means that the class doesn't apply. |
|
482 """ |
|
483 |
|
484 @property |
|
485 def func_name(self): |
|
486 # backward compatibility |
|
487 return self.__class__.__name__ |
|
488 |
|
489 def search_selector(self, selector): |
|
490 """search for the given selector or selector instance in the selectors |
|
491 tree. Return it of None if not found |
|
492 """ |
|
493 if self is selector: |
|
494 return self |
|
495 if isinstance(selector, type) and isinstance(self, selector): |
|
496 return self |
|
497 return None |
|
498 |
|
499 def __str__(self): |
|
500 return self.__class__.__name__ |
|
501 |
|
502 def __and__(self, other): |
|
503 return AndSelector(self, other) |
|
504 def __rand__(self, other): |
|
505 return AndSelector(other, self) |
|
506 |
|
507 def __or__(self, other): |
|
508 return OrSelector(self, other) |
|
509 def __ror__(self, other): |
|
510 return OrSelector(other, self) |
|
511 |
|
512 def __invert__(self): |
|
513 return NotSelector(self) |
|
514 |
|
515 # XXX (function | function) or (function & function) not managed yet |
|
516 |
|
517 def __call__(self, cls, *args, **kwargs): |
|
518 return NotImplementedError("selector %s must implement its logic " |
|
519 "in its __call__ method" % self.__class__) |
|
520 |
|
521 class MultiSelector(Selector): |
|
522 """base class for compound selector classes""" |
|
523 |
|
524 def __init__(self, *selectors): |
|
525 self.selectors = self.merge_selectors(selectors) |
|
526 |
|
527 def __str__(self): |
|
528 return '%s(%s)' % (self.__class__.__name__, |
|
529 ','.join(str(s) for s in self.selectors)) |
|
530 |
|
531 @classmethod |
|
532 def merge_selectors(cls, selectors): |
|
533 """deal with selector instanciation when necessary and merge |
|
534 multi-selectors if possible: |
|
535 |
|
536 AndSelector(AndSelector(sel1, sel2), AndSelector(sel3, sel4)) |
|
537 ==> AndSelector(sel1, sel2, sel3, sel4) |
|
538 """ |
|
539 merged_selectors = [] |
|
540 for selector in selectors: |
|
541 try: |
|
542 selector = _instantiate_selector(selector) |
|
543 except: |
|
544 pass |
|
545 #assert isinstance(selector, Selector), selector |
|
546 if isinstance(selector, cls): |
|
547 merged_selectors += selector.selectors |
|
548 else: |
|
549 merged_selectors.append(selector) |
|
550 return merged_selectors |
|
551 |
|
552 def search_selector(self, selector): |
|
553 """search for the given selector or selector instance in the selectors |
|
554 tree. Return it of None if not found |
|
555 """ |
|
556 for childselector in self.selectors: |
|
557 if childselector is selector: |
|
558 return childselector |
|
559 found = childselector.search_selector(selector) |
|
560 if found is not None: |
|
561 return found |
|
562 return None |
|
563 |
|
564 |
|
565 def objectify_selector(selector_func): |
|
566 """convenience decorator for simple selectors where a class definition |
|
567 would be overkill:: |
|
568 |
|
569 @objectify_selector |
|
570 def yes(cls, *args, **kwargs): |
|
571 return 1 |
|
572 |
|
573 """ |
|
574 return type(selector_func.__name__, (Selector,), |
|
575 {'__call__': lambda self, *args, **kwargs: selector_func(*args, **kwargs)}) |
|
576 |
|
577 def _instantiate_selector(selector): |
|
578 """ensures `selector` is a `Selector` instance |
|
579 |
|
580 NOTE: This should only be used locally in build___select__() |
|
581 XXX: then, why not do it ?? |
|
582 """ |
|
583 if isinstance(selector, types.FunctionType): |
|
584 return objectify_selector(selector)() |
|
585 if isinstance(selector, type) and issubclass(selector, Selector): |
|
586 return selector() |
|
587 return selector |
|
588 |
|
589 |
|
590 class AndSelector(MultiSelector): |
|
591 """and-chained selectors (formerly known as chainall)""" |
|
592 def __call__(self, cls, *args, **kwargs): |
|
593 score = 0 |
|
594 for selector in self.selectors: |
|
595 partscore = selector(cls, *args, **kwargs) |
|
596 if not partscore: |
|
597 return 0 |
|
598 score += partscore |
|
599 return score |
|
600 |
|
601 |
|
602 class OrSelector(MultiSelector): |
|
603 """or-chained selectors (formerly known as chainfirst)""" |
|
604 def __call__(self, cls, *args, **kwargs): |
|
605 for selector in self.selectors: |
|
606 partscore = selector(cls, *args, **kwargs) |
|
607 if partscore: |
|
608 return partscore |
|
609 return 0 |
|
610 |
|
611 class NotSelector(Selector): |
|
612 """negation selector""" |
|
613 def __init__(self, selector): |
|
614 self.selector = selector |
|
615 |
|
616 def __call__(self, cls, *args, **kwargs): |
|
617 score = self.selector(cls, *args, **kwargs) |
|
618 return int(not score) |
|
619 |
|
620 def __str__(self): |
|
621 return 'NOT(%s)' % super(NotSelector, self).__str__() |
|
622 |
448 |
623 |
449 |
624 # XXX bw compat functions ##################################################### |
450 # XXX bw compat functions ##################################################### |
625 |
451 |
|
452 from cubicweb.appobject import objectify_selector, AndSelector, OrSelector, Selector |
|
453 |
|
454 objectify_selector = deprecated('objectify_selector has been moved to appobject module')(objectify_selector) |
|
455 |
|
456 Selector = class_moved(Selector) |
|
457 |
|
458 @deprecated('use & operator (binary and)') |
626 def chainall(*selectors, **kwargs): |
459 def chainall(*selectors, **kwargs): |
627 """return a selector chaining given selectors. If one of |
460 """return a selector chaining given selectors. If one of |
628 the selectors fail, selection will fail, else the returned score |
461 the selectors fail, selection will fail, else the returned score |
629 will be the sum of each selector'score |
462 will be the sum of each selector'score |
630 """ |
463 """ |