13 # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more |
13 # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more |
14 # details. |
14 # details. |
15 # |
15 # |
16 # You should have received a copy of the GNU Lesser General Public License along |
16 # You should have received a copy of the GNU Lesser General Public License along |
17 # with CubicWeb. If not, see <http://www.gnu.org/licenses/>. |
17 # with CubicWeb. If not, see <http://www.gnu.org/licenses/>. |
18 """.. VRegistry: |
18 """.. RegistryStore: |
19 |
19 |
20 The `VRegistry` |
20 The `RegistryStore` |
21 --------------- |
21 ------------------- |
22 |
22 |
23 The `VRegistry` can be seen as a two-level dictionary. It contains |
23 The `RegistryStore` can be seen as a two-level dictionary. It contains |
24 all dynamically loaded objects (subclasses of :ref:`appobject`) to |
24 all dynamically loaded objects (subclasses of :ref:`appobject`) to |
25 build a |cubicweb| application. Basically: |
25 build a |cubicweb| application. Basically: |
26 |
26 |
27 * the first level key returns a *registry*. This key corresponds to the |
27 * the first level key returns a *registry*. This key corresponds to the |
28 `__registry__` attribute of application object classes |
28 `__registry__` attribute of application object classes |
32 attribute of application object classes. |
32 attribute of application object classes. |
33 |
33 |
34 A *registry* holds a specific kind of application objects. There is |
34 A *registry* holds a specific kind of application objects. There is |
35 for instance a registry for entity classes, another for views, etc... |
35 for instance a registry for entity classes, another for views, etc... |
36 |
36 |
37 The `VRegistry` has two main responsibilities: |
37 The `RegistryStore` has two main responsibilities: |
38 |
38 |
39 - being the access point to all registries |
39 - being the access point to all registries |
40 |
40 |
41 - handling the registration process at startup time, and during automatic |
41 - handling the registration process at startup time, and during automatic |
42 reloading in debug mode. |
42 reloading in debug mode. |
74 |
74 |
75 API for objects registration |
75 API for objects registration |
76 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
76 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
77 |
77 |
78 Here are the registration methods that you can use in the `registration_callback` |
78 Here are the registration methods that you can use in the `registration_callback` |
79 to register your objects to the `VRegistry` instance given as argument (usually |
79 to register your objects to the `RegistryStore` instance given as argument (usually |
80 named `vreg`): |
80 named `vreg`): |
81 |
81 |
82 .. automethod:: cubicweb.cwvreg.CubicWebVRegistry.register_all |
82 .. automethod:: cubicweb.cwvreg.CWRegistryStore.register_all |
83 .. automethod:: cubicweb.cwvreg.CubicWebVRegistry.register_and_replace |
83 .. automethod:: cubicweb.cwvreg.CWRegistryStore.register_and_replace |
84 .. automethod:: cubicweb.cwvreg.CubicWebVRegistry.register |
84 .. automethod:: cubicweb.cwvreg.CWRegistryStore.register |
85 .. automethod:: cubicweb.cwvreg.CubicWebVRegistry.unregister |
85 .. automethod:: cubicweb.cwvreg.CWRegistryStore.unregister |
86 |
86 |
87 Examples: |
87 Examples: |
88 |
88 |
89 .. sourcecode:: python |
89 .. sourcecode:: python |
90 |
90 |
191 """ |
191 """ |
192 |
192 |
193 __docformat__ = "restructuredtext en" |
193 __docformat__ = "restructuredtext en" |
194 _ = unicode |
194 _ = unicode |
195 |
195 |
|
196 import sys |
|
197 from os.path import join, dirname, realpath |
196 from warnings import warn |
198 from warnings import warn |
197 from datetime import datetime, date, time, timedelta |
199 from datetime import datetime, date, time, timedelta |
198 |
200 |
199 from logilab.common.decorators import cached, clear_cache |
201 from logilab.common.decorators import cached, clear_cache |
200 from logilab.common.deprecation import deprecated, class_deprecated |
202 from logilab.common.deprecation import deprecated, class_deprecated |
201 from logilab.common.modutils import cleanup_sys_modules |
203 from logilab.common.modutils import cleanup_sys_modules |
|
204 from logilab.common.registry import ( |
|
205 RegistryStore, Registry, classid, |
|
206 ObjectNotFound, NoSelectableObject, RegistryNotFound) |
202 |
207 |
203 from rql import RQLHelper |
208 from rql import RQLHelper |
204 from yams.constraints import BASE_CONVERTERS |
209 from yams.constraints import BASE_CONVERTERS |
205 |
210 |
206 from cubicweb import (ETYPE_NAME_MAP, Binary, UnknownProperty, UnknownEid, |
211 from cubicweb import (CW_SOFTWARE_ROOT, ETYPE_NAME_MAP, CW_EVENT_MANAGER, |
207 ObjectNotFound, NoSelectableObject, RegistryNotFound, |
212 Binary, UnknownProperty, UnknownEid) |
208 CW_EVENT_MANAGER) |
|
209 from cubicweb.vregistry import VRegistry, Registry, class_regid, classid |
|
210 from cubicweb.rtags import RTAGS |
213 from cubicweb.rtags import RTAGS |
|
214 from cubicweb.predicates import (implements, appobject_selectable, |
|
215 _reset_is_instance_cache) |
211 |
216 |
212 def clear_rtag_objects(): |
217 def clear_rtag_objects(): |
213 for rtag in RTAGS: |
218 for rtag in RTAGS: |
214 rtag.clear() |
219 rtag.clear() |
215 |
220 |
216 def use_interfaces(obj): |
221 def use_interfaces(obj): |
217 """return interfaces used by the given object by searching for implements |
222 """return interfaces required by the given object by searching for |
218 selectors |
223 `implements` predicate |
219 """ |
224 """ |
220 from cubicweb.selectors import implements |
|
221 impl = obj.__select__.search_selector(implements) |
225 impl = obj.__select__.search_selector(implements) |
222 if impl: |
226 if impl: |
223 return sorted(impl.expected_ifaces) |
227 return sorted(impl.expected_ifaces) |
224 return () |
228 return () |
225 |
229 |
226 def require_appobject(obj): |
230 def require_appobject(obj): |
227 """return interfaces used by the given object by searching for implements |
231 """return appobjects required by the given object by searching for |
228 selectors |
232 `appobject_selectable` predicate |
229 """ |
233 """ |
230 from cubicweb.selectors import appobject_selectable |
|
231 impl = obj.__select__.search_selector(appobject_selectable) |
234 impl = obj.__select__.search_selector(appobject_selectable) |
232 if impl: |
235 if impl: |
233 return (impl.registry, impl.regids) |
236 return (impl.registry, impl.regids) |
234 return None |
237 return None |
235 |
238 |
251 return sorted([x for x in self.possible_objects(*args, **kwargs) |
254 return sorted([x for x in self.possible_objects(*args, **kwargs) |
252 if x.cw_propval('visible')], |
255 if x.cw_propval('visible')], |
253 key=lambda x: x.cw_propval('order')) |
256 key=lambda x: x.cw_propval('order')) |
254 |
257 |
255 |
258 |
256 VRegistry.REGISTRY_FACTORY[None] = CWRegistry |
|
257 |
|
258 |
259 |
259 class ETypeRegistry(CWRegistry): |
260 class ETypeRegistry(CWRegistry): |
260 |
261 |
261 def clear_caches(self): |
262 def clear_caches(self): |
262 clear_cache(self, 'etype_class') |
263 clear_cache(self, 'etype_class') |
263 clear_cache(self, 'parent_classes') |
264 clear_cache(self, 'parent_classes') |
264 from cubicweb import selectors |
265 _reset_is_instance_cache(self.vreg) |
265 selectors._reset_is_instance_cache(self.vreg) |
|
266 |
266 |
267 def initialization_completed(self): |
267 def initialization_completed(self): |
268 """on registration completed, clear etype_class internal cache |
268 """on registration completed, clear etype_class internal cache |
269 """ |
269 """ |
270 super(ETypeRegistry, self).initialization_completed() |
270 super(ETypeRegistry, self).initialization_completed() |
271 # clear etype cache if you don't want to run into deep weirdness |
271 # clear etype cache if you don't want to run into deep weirdness |
272 self.clear_caches() |
272 self.clear_caches() |
273 |
273 |
274 def register(self, obj, **kwargs): |
274 def register(self, obj, **kwargs): |
275 oid = kwargs.get('oid') or class_regid(obj) |
275 oid = kwargs.get('oid') or obj.__regid__ |
276 if oid != 'Any' and not oid in self.schema: |
276 if oid != 'Any' and not oid in self.schema: |
277 self.error('don\'t register %s, %s type not defined in the ' |
277 self.error('don\'t register %s, %s type not defined in the ' |
278 'schema', obj, oid) |
278 'schema', obj, oid) |
279 return |
279 return |
280 kwargs['clear'] = True |
280 kwargs['clear'] = True |
352 for ttype in targettypes: |
352 for ttype in targettypes: |
353 etypecls = self.etype_class(ttype) |
353 etypecls = self.etype_class(ttype) |
354 fetchattrs_list.append(set(etypecls.fetch_attrs)) |
354 fetchattrs_list.append(set(etypecls.fetch_attrs)) |
355 return reduce(set.intersection, fetchattrs_list) |
355 return reduce(set.intersection, fetchattrs_list) |
356 |
356 |
357 VRegistry.REGISTRY_FACTORY['etypes'] = ETypeRegistry |
|
358 |
|
359 |
357 |
360 class ViewsRegistry(CWRegistry): |
358 class ViewsRegistry(CWRegistry): |
361 |
359 |
362 def main_template(self, req, oid='main-template', rset=None, **kwargs): |
360 def main_template(self, req, oid='main-template', rset=None, **kwargs): |
363 """display query by calling the given template (default to main), |
361 """display query by calling the given template (default to main), |
387 yield view |
385 yield view |
388 except Exception: |
386 except Exception: |
389 self.exception('error while trying to select %s view for %s', |
387 self.exception('error while trying to select %s view for %s', |
390 vid, rset) |
388 vid, rset) |
391 |
389 |
392 VRegistry.REGISTRY_FACTORY['views'] = ViewsRegistry |
|
393 |
|
394 |
390 |
395 class ActionsRegistry(CWRegistry): |
391 class ActionsRegistry(CWRegistry): |
396 def poss_visible_objects(self, *args, **kwargs): |
392 def poss_visible_objects(self, *args, **kwargs): |
397 """return an ordered list of possible actions""" |
393 """return an ordered list of possible actions""" |
398 return sorted(self.possible_objects(*args, **kwargs), |
394 return sorted(self.possible_objects(*args, **kwargs), |
405 actions = rset.possible_actions(**kwargs) # cached implementation |
401 actions = rset.possible_actions(**kwargs) # cached implementation |
406 result = {} |
402 result = {} |
407 for action in actions: |
403 for action in actions: |
408 result.setdefault(action.category, []).append(action) |
404 result.setdefault(action.category, []).append(action) |
409 return result |
405 return result |
410 |
|
411 VRegistry.REGISTRY_FACTORY['actions'] = ActionsRegistry |
|
412 |
406 |
413 |
407 |
414 class CtxComponentsRegistry(CWRegistry): |
408 class CtxComponentsRegistry(CWRegistry): |
415 def poss_visible_objects(self, *args, **kwargs): |
409 def poss_visible_objects(self, *args, **kwargs): |
416 """return an ordered list of possible components""" |
410 """return an ordered list of possible components""" |
443 # XXX set context for bw compat (should now be taken by comp.render()) |
437 # XXX set context for bw compat (should now be taken by comp.render()) |
444 for component in thisctxcomps: |
438 for component in thisctxcomps: |
445 component.cw_extra_kwargs['context'] = context |
439 component.cw_extra_kwargs['context'] = context |
446 return thisctxcomps |
440 return thisctxcomps |
447 |
441 |
448 VRegistry.REGISTRY_FACTORY['ctxcomponents'] = CtxComponentsRegistry |
|
449 |
|
450 |
442 |
451 class BwCompatCWRegistry(object): |
443 class BwCompatCWRegistry(object): |
452 def __init__(self, vreg, oldreg, redirecttoreg): |
444 def __init__(self, vreg, oldreg, redirecttoreg): |
453 self.vreg = vreg |
445 self.vreg = vreg |
454 self.oldreg = oldreg |
446 self.oldreg = oldreg |
460 return getattr(self.vreg[self.redirecto], attr) |
452 return getattr(self.vreg[self.redirecto], attr) |
461 |
453 |
462 def clear(self): pass |
454 def clear(self): pass |
463 def initialization_completed(self): pass |
455 def initialization_completed(self): pass |
464 |
456 |
465 class CubicWebVRegistry(VRegistry): |
457 |
|
458 class CWRegistryStore(RegistryStore): |
466 """Central registry for the cubicweb instance, extending the generic |
459 """Central registry for the cubicweb instance, extending the generic |
467 VRegistry with some cubicweb specific stuff. |
460 RegistryStore with some cubicweb specific stuff. |
468 |
461 |
469 This is one of the central object in cubicweb instance, coupling |
462 This is one of the central object in cubicweb instance, coupling |
470 dynamically loaded objects with the schema and the configuration objects. |
463 dynamically loaded objects with the schema and the configuration objects. |
471 |
464 |
472 It specializes the VRegistry by adding some convenience methods to access to |
465 It specializes the RegistryStore by adding some convenience methods to access to |
473 stored objects. Currently we have the following registries of objects known |
466 stored objects. Currently we have the following registries of objects known |
474 by the web instance (library may use some others additional registries): |
467 by the web instance (library may use some others additional registries): |
475 |
468 |
476 * 'etypes', entity type classes |
469 * 'etypes', entity type classes |
477 |
470 |
489 * 'formrenderers', rendering forms to html |
482 * 'formrenderers', rendering forms to html |
490 |
483 |
491 * 'controllers', primary objects to handle request publishing, directly |
484 * 'controllers', primary objects to handle request publishing, directly |
492 plugged into the application |
485 plugged into the application |
493 """ |
486 """ |
|
487 |
|
488 REGISTRY_FACTORY = {None: CWRegistry, |
|
489 'etypes': ETypeRegistry, |
|
490 'views': ViewsRegistry, |
|
491 'actions': ActionsRegistry, |
|
492 'ctxcomponents': CtxComponentsRegistry, |
|
493 } |
494 |
494 |
495 def __init__(self, config, initlog=True): |
495 def __init__(self, config, initlog=True): |
496 if initlog: |
496 if initlog: |
497 # first init log service |
497 # first init log service |
498 config.init_log() |
498 config.init_log() |
499 super(CubicWebVRegistry, self).__init__(config) |
499 super(CWRegistryStore, self).__init__(config.debugmode) |
|
500 self.config = config |
|
501 # need to clean sys.path this to avoid import confusion pb (i.e. having |
|
502 # the same module loaded as 'cubicweb.web.views' subpackage and as |
|
503 # views' or 'web.views' subpackage. This is mainly for testing purpose, |
|
504 # we should'nt need this in production environment |
|
505 for webdir in (join(dirname(realpath(__file__)), 'web'), |
|
506 join(dirname(__file__), 'web')): |
|
507 if webdir in sys.path: |
|
508 sys.path.remove(webdir) |
|
509 if CW_SOFTWARE_ROOT in sys.path: |
|
510 sys.path.remove(CW_SOFTWARE_ROOT) |
500 self.schema = None |
511 self.schema = None |
501 self.initialized = False |
512 self.initialized = False |
502 # XXX give force_reload (or refactor [re]loading...) |
513 # XXX give force_reload (or refactor [re]loading...) |
503 if self.config.mode != 'test': |
514 if self.config.mode != 'test': |
504 # don't clear rtags during test, this may cause breakage with |
515 # don't clear rtags during test, this may cause breakage with |
513 except RegistryNotFound: |
524 except RegistryNotFound: |
514 self[regid] = self.registry_class(regid)(self) |
525 self[regid] = self.registry_class(regid)(self) |
515 return self[regid] |
526 return self[regid] |
516 |
527 |
517 def items(self): |
528 def items(self): |
518 return [item for item in super(CubicWebVRegistry, self).items() |
529 return [item for item in super(CWRegistryStore, self).items() |
519 if not item[0] in ('propertydefs', 'propertyvalues')] |
530 if not item[0] in ('propertydefs', 'propertyvalues')] |
520 def iteritems(self): |
531 def iteritems(self): |
521 return (item for item in super(CubicWebVRegistry, self).iteritems() |
532 return (item for item in super(CWRegistryStore, self).iteritems() |
522 if not item[0] in ('propertydefs', 'propertyvalues')) |
533 if not item[0] in ('propertydefs', 'propertyvalues')) |
523 |
534 |
524 def values(self): |
535 def values(self): |
525 return [value for key, value in self.items()] |
536 return [value for key, value in self.items()] |
526 def itervalues(self): |
537 def itervalues(self): |
527 return (value for key, value in self.items()) |
538 return (value for key, value in self.items()) |
528 |
539 |
529 def reset(self): |
540 def reset(self): |
530 CW_EVENT_MANAGER.emit('before-registry-reset', self) |
541 CW_EVENT_MANAGER.emit('before-registry-reset', self) |
531 super(CubicWebVRegistry, self).reset() |
542 super(CWRegistryStore, self).reset() |
532 self._needs_iface = {} |
543 self._needs_iface = {} |
533 self._needs_appobject = {} |
544 self._needs_appobject = {} |
534 # two special registries, propertydefs which care all the property |
545 # two special registries, propertydefs which care all the property |
535 # definitions, and propertyvals which contains values for those |
546 # definitions, and propertyvals which contains values for those |
536 # properties |
547 # properties |
595 def register_if_interface_found(self, obj, ifaces, **kwargs): |
606 def register_if_interface_found(self, obj, ifaces, **kwargs): |
596 """register `obj` but remove it if no entity class implements one of |
607 """register `obj` but remove it if no entity class implements one of |
597 the given `ifaces` interfaces at the end of the registration process. |
608 the given `ifaces` interfaces at the end of the registration process. |
598 |
609 |
599 Extra keyword arguments are given to the |
610 Extra keyword arguments are given to the |
600 :meth:`~cubicweb.cwvreg.CubicWebVRegistry.register` function. |
611 :meth:`~cubicweb.cwvreg.CWRegistryStore.register` function. |
601 """ |
612 """ |
602 self.register(obj, **kwargs) |
613 self.register(obj, **kwargs) |
603 if not isinstance(ifaces, (tuple, list)): |
614 if not isinstance(ifaces, (tuple, list)): |
604 self._needs_iface[obj] = (ifaces,) |
615 self._needs_iface[obj] = (ifaces,) |
605 else: |
616 else: |
611 `obj.__regid__` if not specified. |
622 `obj.__regid__` if not specified. |
612 |
623 |
613 If `clear` is true, all objects with the same identifier will be |
624 If `clear` is true, all objects with the same identifier will be |
614 previously unregistered. |
625 previously unregistered. |
615 """ |
626 """ |
616 super(CubicWebVRegistry, self).register(obj, *args, **kwargs) |
627 super(CWRegistryStore, self).register(obj, *args, **kwargs) |
617 # XXX bw compat |
628 # XXX bw compat |
618 ifaces = use_interfaces(obj) |
629 ifaces = use_interfaces(obj) |
619 if ifaces: |
630 if ifaces: |
620 if not obj.__name__.endswith('Adapter') and \ |
631 if not obj.__name__.endswith('Adapter') and \ |
621 any(iface for iface in ifaces if not isinstance(iface, basestring)): |
632 any(iface for iface in ifaces if not isinstance(iface, basestring)): |
628 self._needs_appobject[obj] = depends_on |
639 self._needs_appobject[obj] = depends_on |
629 |
640 |
630 def register_objects(self, path): |
641 def register_objects(self, path): |
631 """overriden to give cubicweb's extrapath (eg cubes package's __path__) |
642 """overriden to give cubicweb's extrapath (eg cubes package's __path__) |
632 """ |
643 """ |
633 super(CubicWebVRegistry, self).register_objects( |
644 super(CWRegistryStore, self).register_objects( |
634 path, self.config.extrapath) |
645 path, self.config.extrapath) |
635 |
646 |
636 def initialization_completed(self): |
647 def initialization_completed(self): |
637 """cw specific code once vreg initialization is completed: |
648 """cw specific code once vreg initialization is completed: |
638 |
649 |
683 break |
694 break |
684 else: |
695 else: |
685 self.debug('unregister %s (no %s object in registry %s)', |
696 self.debug('unregister %s (no %s object in registry %s)', |
686 classid(obj), ' or '.join(regids), regname) |
697 classid(obj), ' or '.join(regids), regname) |
687 self.unregister(obj) |
698 self.unregister(obj) |
688 super(CubicWebVRegistry, self).initialization_completed() |
699 super(CWRegistryStore, self).initialization_completed() |
689 for rtag in RTAGS: |
700 for rtag in RTAGS: |
690 # don't check rtags if we don't want to cleanup_interface_sobjects |
701 # don't check rtags if we don't want to cleanup_interface_sobjects |
691 rtag.init(self.schema, check=self.config.cleanup_interface_sobjects) |
702 rtag.init(self.schema, check=self.config.cleanup_interface_sobjects) |
692 |
703 |
693 # rql parsing utilities #################################################### |
704 # rql parsing utilities #################################################### |