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 |
259 def related_appobject(obj, appobjectattr='__appobject__'): |
|
260 """ adapts any object to a potential appobject bound to it |
|
261 through the __appobject__ attribute |
|
262 """ |
|
263 return getattr(obj, appobjectattr, obj) |
257 |
264 |
258 |
265 |
259 class ETypeRegistry(CWRegistry): |
266 class ETypeRegistry(CWRegistry): |
260 |
267 |
261 def clear_caches(self): |
268 def clear_caches(self): |
262 clear_cache(self, 'etype_class') |
269 clear_cache(self, 'etype_class') |
263 clear_cache(self, 'parent_classes') |
270 clear_cache(self, 'parent_classes') |
264 from cubicweb import selectors |
271 _reset_is_instance_cache(self.vreg) |
265 selectors._reset_is_instance_cache(self.vreg) |
|
266 |
272 |
267 def initialization_completed(self): |
273 def initialization_completed(self): |
268 """on registration completed, clear etype_class internal cache |
274 """on registration completed, clear etype_class internal cache |
269 """ |
275 """ |
270 super(ETypeRegistry, self).initialization_completed() |
276 super(ETypeRegistry, self).initialization_completed() |
271 # clear etype cache if you don't want to run into deep weirdness |
277 # clear etype cache if you don't want to run into deep weirdness |
272 self.clear_caches() |
278 self.clear_caches() |
273 |
279 |
274 def register(self, obj, **kwargs): |
280 def register(self, obj, **kwargs): |
275 oid = kwargs.get('oid') or class_regid(obj) |
281 obj = related_appobject(obj) |
|
282 oid = kwargs.get('oid') or obj.__regid__ |
276 if oid != 'Any' and not oid in self.schema: |
283 if oid != 'Any' and not oid in self.schema: |
277 self.error('don\'t register %s, %s type not defined in the ' |
284 self.error('don\'t register %s, %s type not defined in the ' |
278 'schema', obj, oid) |
285 'schema', obj, oid) |
279 return |
286 return |
280 kwargs['clear'] = True |
287 kwargs['clear'] = True |
352 for ttype in targettypes: |
359 for ttype in targettypes: |
353 etypecls = self.etype_class(ttype) |
360 etypecls = self.etype_class(ttype) |
354 fetchattrs_list.append(set(etypecls.fetch_attrs)) |
361 fetchattrs_list.append(set(etypecls.fetch_attrs)) |
355 return reduce(set.intersection, fetchattrs_list) |
362 return reduce(set.intersection, fetchattrs_list) |
356 |
363 |
357 VRegistry.REGISTRY_FACTORY['etypes'] = ETypeRegistry |
|
358 |
|
359 |
364 |
360 class ViewsRegistry(CWRegistry): |
365 class ViewsRegistry(CWRegistry): |
361 |
366 |
362 def main_template(self, req, oid='main-template', rset=None, **kwargs): |
367 def main_template(self, req, oid='main-template', rset=None, **kwargs): |
363 """display query by calling the given template (default to main), |
368 """display query by calling the given template (default to main), |
387 yield view |
392 yield view |
388 except Exception: |
393 except Exception: |
389 self.exception('error while trying to select %s view for %s', |
394 self.exception('error while trying to select %s view for %s', |
390 vid, rset) |
395 vid, rset) |
391 |
396 |
392 VRegistry.REGISTRY_FACTORY['views'] = ViewsRegistry |
|
393 |
|
394 |
397 |
395 class ActionsRegistry(CWRegistry): |
398 class ActionsRegistry(CWRegistry): |
396 def poss_visible_objects(self, *args, **kwargs): |
399 def poss_visible_objects(self, *args, **kwargs): |
397 """return an ordered list of possible actions""" |
400 """return an ordered list of possible actions""" |
398 return sorted(self.possible_objects(*args, **kwargs), |
401 return sorted(self.possible_objects(*args, **kwargs), |
405 actions = rset.possible_actions(**kwargs) # cached implementation |
408 actions = rset.possible_actions(**kwargs) # cached implementation |
406 result = {} |
409 result = {} |
407 for action in actions: |
410 for action in actions: |
408 result.setdefault(action.category, []).append(action) |
411 result.setdefault(action.category, []).append(action) |
409 return result |
412 return result |
410 |
|
411 VRegistry.REGISTRY_FACTORY['actions'] = ActionsRegistry |
|
412 |
413 |
413 |
414 |
414 class CtxComponentsRegistry(CWRegistry): |
415 class CtxComponentsRegistry(CWRegistry): |
415 def poss_visible_objects(self, *args, **kwargs): |
416 def poss_visible_objects(self, *args, **kwargs): |
416 """return an ordered list of possible components""" |
417 """return an ordered list of possible components""" |
443 # XXX set context for bw compat (should now be taken by comp.render()) |
444 # XXX set context for bw compat (should now be taken by comp.render()) |
444 for component in thisctxcomps: |
445 for component in thisctxcomps: |
445 component.cw_extra_kwargs['context'] = context |
446 component.cw_extra_kwargs['context'] = context |
446 return thisctxcomps |
447 return thisctxcomps |
447 |
448 |
448 VRegistry.REGISTRY_FACTORY['ctxcomponents'] = CtxComponentsRegistry |
|
449 |
|
450 |
449 |
451 class BwCompatCWRegistry(object): |
450 class BwCompatCWRegistry(object): |
452 def __init__(self, vreg, oldreg, redirecttoreg): |
451 def __init__(self, vreg, oldreg, redirecttoreg): |
453 self.vreg = vreg |
452 self.vreg = vreg |
454 self.oldreg = oldreg |
453 self.oldreg = oldreg |
460 return getattr(self.vreg[self.redirecto], attr) |
459 return getattr(self.vreg[self.redirecto], attr) |
461 |
460 |
462 def clear(self): pass |
461 def clear(self): pass |
463 def initialization_completed(self): pass |
462 def initialization_completed(self): pass |
464 |
463 |
465 class CubicWebVRegistry(VRegistry): |
464 |
|
465 class CWRegistryStore(RegistryStore): |
466 """Central registry for the cubicweb instance, extending the generic |
466 """Central registry for the cubicweb instance, extending the generic |
467 VRegistry with some cubicweb specific stuff. |
467 RegistryStore with some cubicweb specific stuff. |
468 |
468 |
469 This is one of the central object in cubicweb instance, coupling |
469 This is one of the central object in cubicweb instance, coupling |
470 dynamically loaded objects with the schema and the configuration objects. |
470 dynamically loaded objects with the schema and the configuration objects. |
471 |
471 |
472 It specializes the VRegistry by adding some convenience methods to access to |
472 It specializes the RegistryStore by adding some convenience methods to access to |
473 stored objects. Currently we have the following registries of objects known |
473 stored objects. Currently we have the following registries of objects known |
474 by the web instance (library may use some others additional registries): |
474 by the web instance (library may use some others additional registries): |
475 |
475 |
476 * 'etypes', entity type classes |
476 * 'etypes', entity type classes |
477 |
477 |
489 * 'formrenderers', rendering forms to html |
489 * 'formrenderers', rendering forms to html |
490 |
490 |
491 * 'controllers', primary objects to handle request publishing, directly |
491 * 'controllers', primary objects to handle request publishing, directly |
492 plugged into the application |
492 plugged into the application |
493 """ |
493 """ |
|
494 |
|
495 REGISTRY_FACTORY = {None: CWRegistry, |
|
496 'etypes': ETypeRegistry, |
|
497 'views': ViewsRegistry, |
|
498 'actions': ActionsRegistry, |
|
499 'ctxcomponents': CtxComponentsRegistry, |
|
500 } |
494 |
501 |
495 def __init__(self, config, initlog=True): |
502 def __init__(self, config, initlog=True): |
496 if initlog: |
503 if initlog: |
497 # first init log service |
504 # first init log service |
498 config.init_log() |
505 config.init_log() |
499 super(CubicWebVRegistry, self).__init__(config) |
506 super(CWRegistryStore, self).__init__(config.debugmode) |
|
507 self.config = config |
|
508 # need to clean sys.path this to avoid import confusion pb (i.e. having |
|
509 # the same module loaded as 'cubicweb.web.views' subpackage and as |
|
510 # views' or 'web.views' subpackage. This is mainly for testing purpose, |
|
511 # we should'nt need this in production environment |
|
512 for webdir in (join(dirname(realpath(__file__)), 'web'), |
|
513 join(dirname(__file__), 'web')): |
|
514 if webdir in sys.path: |
|
515 sys.path.remove(webdir) |
|
516 if CW_SOFTWARE_ROOT in sys.path: |
|
517 sys.path.remove(CW_SOFTWARE_ROOT) |
500 self.schema = None |
518 self.schema = None |
501 self.initialized = False |
519 self.initialized = False |
502 # XXX give force_reload (or refactor [re]loading...) |
520 # XXX give force_reload (or refactor [re]loading...) |
503 if self.config.mode != 'test': |
521 if self.config.mode != 'test': |
504 # don't clear rtags during test, this may cause breakage with |
522 # don't clear rtags during test, this may cause breakage with |
513 except RegistryNotFound: |
531 except RegistryNotFound: |
514 self[regid] = self.registry_class(regid)(self) |
532 self[regid] = self.registry_class(regid)(self) |
515 return self[regid] |
533 return self[regid] |
516 |
534 |
517 def items(self): |
535 def items(self): |
518 return [item for item in super(CubicWebVRegistry, self).items() |
536 return [item for item in super(CWRegistryStore, self).items() |
519 if not item[0] in ('propertydefs', 'propertyvalues')] |
537 if not item[0] in ('propertydefs', 'propertyvalues')] |
520 def iteritems(self): |
538 def iteritems(self): |
521 return (item for item in super(CubicWebVRegistry, self).iteritems() |
539 return (item for item in super(CWRegistryStore, self).iteritems() |
522 if not item[0] in ('propertydefs', 'propertyvalues')) |
540 if not item[0] in ('propertydefs', 'propertyvalues')) |
523 |
541 |
524 def values(self): |
542 def values(self): |
525 return [value for key, value in self.items()] |
543 return [value for key, value in self.items()] |
526 def itervalues(self): |
544 def itervalues(self): |
527 return (value for key, value in self.items()) |
545 return (value for key, value in self.items()) |
528 |
546 |
|
547 def load_module(self, module): |
|
548 """ variation from the base implementation: |
|
549 apply related_appobject to the automatically registered objects |
|
550 """ |
|
551 self.info('loading %s from %s', module.__name__, module.__file__) |
|
552 if hasattr(module, 'registration_callback'): |
|
553 module.registration_callback(self) |
|
554 return |
|
555 for objname, obj in vars(module).iteritems(): |
|
556 if objname.startswith('_'): |
|
557 continue |
|
558 self._load_ancestors_then_object(module.__name__, |
|
559 related_appobject(obj)) |
|
560 |
529 def reset(self): |
561 def reset(self): |
530 CW_EVENT_MANAGER.emit('before-registry-reset', self) |
562 CW_EVENT_MANAGER.emit('before-registry-reset', self) |
531 super(CubicWebVRegistry, self).reset() |
563 super(CWRegistryStore, self).reset() |
532 self._needs_iface = {} |
564 self._needs_iface = {} |
533 self._needs_appobject = {} |
565 self._needs_appobject = {} |
534 # two special registries, propertydefs which care all the property |
566 # two special registries, propertydefs which care all the property |
535 # definitions, and propertyvals which contains values for those |
567 # definitions, and propertyvals which contains values for those |
536 # properties |
568 # properties |
538 self['propertydefs'] = {} |
570 self['propertydefs'] = {} |
539 self['propertyvalues'] = self.eprop_values = {} |
571 self['propertyvalues'] = self.eprop_values = {} |
540 for key, propdef in self.config.cwproperty_definitions(): |
572 for key, propdef in self.config.cwproperty_definitions(): |
541 self.register_property(key, **propdef) |
573 self.register_property(key, **propdef) |
542 CW_EVENT_MANAGER.emit('after-registry-reset', self) |
574 CW_EVENT_MANAGER.emit('after-registry-reset', self) |
|
575 |
|
576 def register_all(self, objects, modname, butclasses=()): |
|
577 butclasses = set(related_appobject(obj) |
|
578 for obj in butclasses) |
|
579 objects = [related_appobject(obj) for obj in objects] |
|
580 super(CWRegistryStore, self).register_all(objects, modname, butclasses) |
|
581 |
|
582 def register_and_replace(self, obj, replaced): |
|
583 obj = related_appobject(obj) |
|
584 replaced = related_appobject(replaced) |
|
585 super(CWRegistryStore, self).register_and_replace(obj, replaced) |
543 |
586 |
544 def set_schema(self, schema): |
587 def set_schema(self, schema): |
545 """set instance'schema and load application objects""" |
588 """set instance'schema and load application objects""" |
546 self._set_schema(schema) |
589 self._set_schema(schema) |
547 # now we can load application's web objects |
590 # now we can load application's web objects |
595 def register_if_interface_found(self, obj, ifaces, **kwargs): |
638 def register_if_interface_found(self, obj, ifaces, **kwargs): |
596 """register `obj` but remove it if no entity class implements one of |
639 """register `obj` but remove it if no entity class implements one of |
597 the given `ifaces` interfaces at the end of the registration process. |
640 the given `ifaces` interfaces at the end of the registration process. |
598 |
641 |
599 Extra keyword arguments are given to the |
642 Extra keyword arguments are given to the |
600 :meth:`~cubicweb.cwvreg.CubicWebVRegistry.register` function. |
643 :meth:`~cubicweb.cwvreg.CWRegistryStore.register` function. |
601 """ |
644 """ |
602 self.register(obj, **kwargs) |
645 self.register(obj, **kwargs) |
603 if not isinstance(ifaces, (tuple, list)): |
646 if not isinstance(ifaces, (tuple, list)): |
604 self._needs_iface[obj] = (ifaces,) |
647 self._needs_iface[obj] = (ifaces,) |
605 else: |
648 else: |
611 `obj.__regid__` if not specified. |
654 `obj.__regid__` if not specified. |
612 |
655 |
613 If `clear` is true, all objects with the same identifier will be |
656 If `clear` is true, all objects with the same identifier will be |
614 previously unregistered. |
657 previously unregistered. |
615 """ |
658 """ |
616 super(CubicWebVRegistry, self).register(obj, *args, **kwargs) |
659 obj = related_appobject(obj) |
|
660 super(CWRegistryStore, self).register(obj, *args, **kwargs) |
617 # XXX bw compat |
661 # XXX bw compat |
618 ifaces = use_interfaces(obj) |
662 ifaces = use_interfaces(obj) |
619 if ifaces: |
663 if ifaces: |
620 if not obj.__name__.endswith('Adapter') and \ |
664 if not obj.__name__.endswith('Adapter') and \ |
621 any(iface for iface in ifaces if not isinstance(iface, basestring)): |
665 any(iface for iface in ifaces if not isinstance(iface, basestring)): |
628 self._needs_appobject[obj] = depends_on |
672 self._needs_appobject[obj] = depends_on |
629 |
673 |
630 def register_objects(self, path): |
674 def register_objects(self, path): |
631 """overriden to give cubicweb's extrapath (eg cubes package's __path__) |
675 """overriden to give cubicweb's extrapath (eg cubes package's __path__) |
632 """ |
676 """ |
633 super(CubicWebVRegistry, self).register_objects( |
677 super(CWRegistryStore, self).register_objects( |
634 path, self.config.extrapath) |
678 path, self.config.extrapath) |
635 |
679 |
636 def initialization_completed(self): |
680 def initialization_completed(self): |
637 """cw specific code once vreg initialization is completed: |
681 """cw specific code once vreg initialization is completed: |
638 |
682 |
683 break |
727 break |
684 else: |
728 else: |
685 self.debug('unregister %s (no %s object in registry %s)', |
729 self.debug('unregister %s (no %s object in registry %s)', |
686 classid(obj), ' or '.join(regids), regname) |
730 classid(obj), ' or '.join(regids), regname) |
687 self.unregister(obj) |
731 self.unregister(obj) |
688 super(CubicWebVRegistry, self).initialization_completed() |
732 super(CWRegistryStore, self).initialization_completed() |
689 for rtag in RTAGS: |
733 for rtag in RTAGS: |
690 # don't check rtags if we don't want to cleanup_interface_sobjects |
734 # don't check rtags if we don't want to cleanup_interface_sobjects |
691 rtag.init(self.schema, check=self.config.cleanup_interface_sobjects) |
735 rtag.init(self.schema, check=self.config.cleanup_interface_sobjects) |
692 |
736 |
693 # rql parsing utilities #################################################### |
737 # rql parsing utilities #################################################### |