# HG changeset patch # User Sylvain Thénault # Date 1270217435 -7200 # Node ID 43afbdd5c8b4aff7a04e9aa806b5b89bbb58f099 # Parent 0aa4d348c2e6accd1e5b6c17560bdd9ece2b8f36 improved doc on selectors an vregistry diff -r 0aa4d348c2e6 -r 43afbdd5c8b4 cwconfig.py --- a/cwconfig.py Fri Apr 02 16:10:17 2010 +0200 +++ b/cwconfig.py Fri Apr 02 16:10:35 2010 +0200 @@ -119,9 +119,6 @@ .. envvar:: CW_RUNTIME_DIR Directory where pid files will be written - - -.. |cubicweb| replace:: *CubicWeb* """ __docformat__ = "restructuredtext en" _ = unicode diff -r 0aa4d348c2e6 -r 43afbdd5c8b4 cwvreg.py --- a/cwvreg.py Fri Apr 02 16:10:17 2010 +0200 +++ b/cwvreg.py Fri Apr 02 16:10:35 2010 +0200 @@ -347,8 +347,11 @@ obj.schema = schema def register_if_interface_found(self, obj, ifaces, **kwargs): - """register an object but remove it if no entity class implements one of - the given interfaces at the end of the registration process + """register `obj` but remove it if no entity class implements one of + the given `ifaces` interfaces at the end of the registration process. + + Extra keyword arguments are given to the + :meth:`~cubicweb.cwvreg.CubicWebVRegistry.register` function. """ self.register(obj, **kwargs) if not isinstance(ifaces, (tuple, list)): @@ -357,6 +360,13 @@ self._needs_iface[obj] = ifaces def register(self, obj, *args, **kwargs): + """register `obj` application object into `registryname` or + `obj.__registry__` if not specified, with identifier `oid` or + `obj.__regid__` if not specified. + + If `clear` is true, all objects with the same identifier will be + previously unregistered. + """ super(CubicWebVRegistry, self).register(obj, *args, **kwargs) # XXX bw compat ifaces = use_interfaces(obj) diff -r 0aa4d348c2e6 -r 43afbdd5c8b4 doc/book/en/development/devcore/appobject.rst --- a/doc/book/en/development/devcore/appobject.rst Fri Apr 02 16:10:17 2010 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,71 +0,0 @@ - - -The `AppObject` class -~~~~~~~~~~~~~~~~~~~~~ - -In general: - -* we do not inherit directly from this class but from a more specific - class such as `AnyEntity`, `EntityView`, `AnyRsetView`, - `Action`... - -* to be recordable, a subclass has to define its own register (attribute - `__registry__`) and its identifier (attribute `id`). Usually we do not have - to take care of the register, only the identifier `id`. - -We can find a certain number of attributes and methods defined in this class -and common to all the application objects. - -At recording time, the following attributes are dynamically added to -the *subclasses*: - -* `vreg`, the `vregistry` of the instance -* `schema`, the instance schema -* `config`, the instance configuration - -We also find on instances, the following attributes: - -* ._cw`, `Request` instance -* `rset`, the *result set* associated to the object if necessary - -:URL handling: - * `build_url(*args, **kwargs)`, returns an absolute URL based on the - given arguments. The *controller* supposed to handle the response, - can be specified through the first positional parameter (the - connection is theoretically done automatically :). - -:Data manipulation: - - * `entity(row, col=0)`, returns the entity corresponding to the data position - in the *result set* associated to the object - - * `complete_entity(row, col=0, skip_bytes=True)`, is equivalent to `entity` but - also call the method `complete()` on the entity before returning it - -:Data formatting: - * `format_date(date, date_format=None, time=False)` returns a string for a - date time according to instance's configuration - * `format_time(time)` returns a string for a date time according to - instance's configuration - -:And more...: - - * `tal_render(template, variables)`, renders a precompiled page template with - variables in the given dictionary as context - -.. note:: - When we inherit from `AppObject` (even not directly), you *always* have to use - **super()** to get the methods and attributes of the superclasses, and not - use the class identifier. - - For example, instead of writting: :: - - class Truc(PrimaryView): - def f(self, arg1): - PrimaryView.f(self, arg1) - - You must write: :: - - class Truc(PrimaryView): - def f(self, arg1): - super(Truc, self).f(arg1) diff -r 0aa4d348c2e6 -r 43afbdd5c8b4 doc/book/en/development/devcore/cwconfig.rst --- a/doc/book/en/development/devcore/cwconfig.rst Fri Apr 02 16:10:17 2010 +0200 +++ b/doc/book/en/development/devcore/cwconfig.rst Fri Apr 02 16:10:35 2010 +0200 @@ -1,5 +1,5 @@ :mod:`Configuration ` ---------------------------------------- -.. automodule:: cubicweb.cwconfig - :members: +.. .. automodule:: cubicweb.cwconfig +.. :members: diff -r 0aa4d348c2e6 -r 43afbdd5c8b4 doc/book/en/development/devcore/index.rst --- a/doc/book/en/development/devcore/index.rst Fri Apr 02 16:10:17 2010 +0200 +++ b/doc/book/en/development/devcore/index.rst Fri Apr 02 16:10:35 2010 +0200 @@ -4,9 +4,6 @@ .. toctree:: :maxdepth: 1 - vreg.rst - appobject.rst - selectors.rst dbapi.rst cwconfig.rst diff -r 0aa4d348c2e6 -r 43afbdd5c8b4 doc/book/en/development/devcore/selectors.rst --- a/doc/book/en/development/devcore/selectors.rst Fri Apr 02 16:10:17 2010 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,89 +0,0 @@ -Base selectors --------------- - -Selectors are scoring functions that are called by the registry to tell whenever -an appobject can be selected in a given context. Selector sets are for instance -the glue that tie views to the data model. Using them appropriately is an -essential part of the construction of well behaved cubes. - -Of course you may have to write your own set of selectors as your needs grows and -you get familiar with the framework (see :ref:`CustomSelectors`). - -Here is a description of generic selectors provided by CubicWeb that should suit -most of your needs. - -Bare selectors -~~~~~~~~~~~~~~ -Those selectors are somewhat dumb, which doesn't mean they're not (very) useful. - -.. autoclass:: cubicweb.appobject.yes -.. autoclass:: cubicweb.selectors.match_kwargs -.. autoclass:: cubicweb.selectors.appobject_selectable - - -Result set selectors -~~~~~~~~~~~~~~~~~~~~~ -Those selectors are looking for a result set in the context ('rset' argument or -the input context) and match or not according to its shape. Some of these -selectors have different behaviour if a particular cell of the result set is -specified using 'row' and 'col' arguments of the input context or not. - -.. autoclass:: cubicweb.selectors.none_rset -.. autoclass:: cubicweb.selectors.any_rset -.. autoclass:: cubicweb.selectors.nonempty_rset -.. autoclass:: cubicweb.selectors.empty_rset -.. autoclass:: cubicweb.selectors.one_line_rset -.. autoclass:: cubicweb.selectors.multi_lines_rset -.. autoclass:: cubicweb.selectors.multi_columns_rset -.. autoclass:: cubicweb.selectors.paginated_rset -.. autoclass:: cubicweb.selectors.sorted_rset -.. autoclass:: cubicweb.selectors.one_etype_rset -.. autoclass:: cubicweb.selectors.multi_etypes_rset - - -Entity selectors -~~~~~~~~~~~~~~~~ -Those selectors are looking for either an `entity` argument in the input context, -or entity found in the result set ('rset' argument or the input context) and -match or not according to entity's (instance or class) properties. - -.. autoclass:: cubicweb.selectors.non_final_entity -.. autoclass:: cubicweb.selectors.implements -.. autoclass:: cubicweb.selectors.score_entity -.. autoclass:: cubicweb.selectors.rql_condition -.. autoclass:: cubicweb.selectors.relation_possible -.. autoclass:: cubicweb.selectors.partial_relation_possible -.. autoclass:: cubicweb.selectors.has_related_entities -.. autoclass:: cubicweb.selectors.partial_has_related_entities -.. autoclass:: cubicweb.selectors.has_permission -.. autoclass:: cubicweb.selectors.has_add_permission - - -Logged user selectors -~~~~~~~~~~~~~~~~~~~~~ -Those selectors are looking for properties of the user issuing the request. - -.. autoclass:: cubicweb.selectors.anonymous_user -.. autoclass:: cubicweb.selectors.authenticated_user -.. autoclass:: cubicweb.selectors.match_user_groups - - -Web request selectors -~~~~~~~~~~~~~~~~~~~~~ -Those selectors are looking for properties of *web* request, they can not be -used on the data repository side. - -.. autoclass:: cubicweb.selectors.match_form_params -.. autoclass:: cubicweb.selectors.match_search_state -.. autoclass:: cubicweb.selectors.match_context_prop -.. autoclass:: cubicweb.selectors.match_view -.. autoclass:: cubicweb.selectors.primary_view -.. autoclass:: cubicweb.selectors.specified_etype_implements - - -Other selectors -~~~~~~~~~~~~~~~ -.. autoclass:: cubicweb.selectors.match_transition - -You'll also find some other (very) specific selectors hidden in other modules -than :mod:`cubicweb.selectors`. diff -r 0aa4d348c2e6 -r 43afbdd5c8b4 doc/book/en/development/devcore/vreg.rst --- a/doc/book/en/development/devcore/vreg.rst Fri Apr 02 16:10:17 2010 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,220 +0,0 @@ -The VRegistry --------------- - -The recording process on startup -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Details of the recording process -```````````````````````````````` - -.. index:: - vregistry: registration_callback - -On startup, |cubicweb| have to fill the vregistry with appobjects defined -in its library and in cubes used by the instance. Appobjects from the library -are loaded first, then appobjects provided by cubes are loaded in an ordered -way (e.g. if your cube depends on an other, appobjects from the dependancy will -be loaded first). Cube's modules or packages where appobject are looked at is explained -in :ref:`cubelayout`. - -For each module: - -* by default all objects are registered automatically - -* if some objects have to replace other objects or be included only if a - condition is true, you'll have to define a `registration_callback(vreg)` - function in your module and explicitly register *all objects* in this - module, using the vregistry api defined below. - -.. note:: - Once the function `registration_callback(vreg)` is implemented, all the objects - have to be explicitly registered as it disables the automatic object registering. - - -API d'enregistrement des objets -``````````````````````````````` -.. automethod:: cubicweb.cwvreg.CubicWebVRegistry.register_all -.. automethod:: cubicweb.cwvreg.CubicWebVRegistry.register_and_replace -.. automethod:: cubicweb.cwvreg.CubicWebVRegistry.register -.. automethod:: cubicweb.cwvreg.CubicWebVRegistry.register_if_interface_found -.. automethod:: cubicweb.cwvreg.CubicWebVRegistry.unregister - - -Examples -```````` -.. sourcecode:: python - - # web/views/basecomponents.py - def registration_callback(vreg): - # register everything in the module except SeeAlsoComponent - vreg.register_all(globals().values(), __name__, (SeeAlsoVComponent,)) - # conditionally register SeeAlsoVComponent - if 'see_also' in vreg.schema: - vreg.register(SeeAlsoVComponent) - - # goa/appobjects/sessions.py - def registration_callback(vreg): - vreg.register(SessionsCleaner) - # replace AuthenticationManager by GAEAuthenticationManager - vreg.register_and_replace(GAEAuthenticationManager, AuthenticationManager) - # replace PersistentSessionManager by GAEPersistentSessionManager - vreg.register_and_replace(GAEPersistentSessionManager, PersistentSessionManager) - - -Runtime objects selection -~~~~~~~~~~~~~~~~~~~~~~~~~ - -Using and combining existant selectors -`````````````````````````````````````` - -The object's selector is defined by its `__select__` class attribute. - -When two selectors are combined using the `&` operator (formerly `chainall`), it -means that both should return a positive score. On success, the sum of scores is returned. - -When two selectors are combined using the `|` operator (former `chainfirst`), it -means that one of them should return a positive score. On success, the first -positive score is returned. - -You can also "negate" a selector by precedeing it by the `~` operator. - -Of course you can use paren to balance expressions. - - -For instance, if you are selecting the primary (eg `__regid__ = 'primary'`) view (eg -`__registry__ = 'view'`) for a result set containing a `Card` entity, 2 objects -will probably be selectable: - -* the default primary view (`__select__ = implements('Any')`), meaning - that the object is selectable for any kind of entity type - -* the specific `Card` primary view (`__select__ = implements('Card')`, - meaning that the object is selectable for Card entities - -Other primary views specific to other entity types won't be selectable -in this case. Among selectable objects, the implements selector will -return a higher score than the second view since it's more specific, -so it will be selected as expected. - - -Example -```````` - -The goal: when on a Blog, one wants the RSS link to refer to blog -entries, not to the blog entity itself. - -To do that, one defines a method on entity classes that returns the -RSS stream url for a given entity. The default implementation on -AnyEntity and a specific implementation on Blog will do what we want. - -But when we have a result set containing several Blog entities (or -different entities), we don't know on which entity to call the -aforementioned method. In this case, we keep the current behaviour -(e.g : call to limited_rql). - -Hence we have two cases here, one for a single-entity rsets, the other -for multi-entities rsets. - -In web/views/boxes.py lies the RSSIconBox class. Look at its selector :: - - class RSSIconBox(ExtResourcesBoxTemplate): - """just display the RSS icon on uniform result set""" - __select__ = ExtResourcesBoxTemplate.__select__ & non_final_entity() - -It takes into account: - -* the inherited selection criteria (one has to look them up in the - class hierarchy to know the details) - -* non_final_entity, which filters on rsets containing non final - entities (a 'final entity' being synonym for entity attribute) - -This matches our second case. Hence we have to provide a specific -component for the first case:: - - class EntityRSSIconBox(RSSIconBox): - """just display the RSS icon on uniform result set for a single entity""" - __select__ = RSSIconBox.__select__ & one_line_rset() - -Here, one adds the one_line_rset selector, which filters result sets -of size 1. When one chains selectors, the final score is the sum of -the score of each individual selector (unless one of them returns 0, -in which case the object is non selectable). Thus, on a multiple -entities selector, one_line_rset makes the EntityRSSIconBox class non -selectable. For an rset with one entity, the EntityRSSIconBox class -will have a higher score then RSSIconBox, which is what we wanted. - -Of course, once this is done, you have to: - -* fill in the call method of EntityRSSIconBox - -* provide the default implementation of the method returning the RSS - stream url on AnyEntity - -* redefine this method on Blog. - -When to use selectors? -`````````````````````` - -Selectors are to be used whenever arises the need of dispatching on the shape or -content of a result set or whatever else context (value in request form params, -authenticated user groups, etc...). That is, almost all the time. - -XXX add and example of a single view w/ big "if" inside splitted into two views -with appropriate selectors. - - -.. CustomSelectors_ - -Defining your own selectors -``````````````````````````` -.. autoclass:: cubicweb.appobject.Selector - :members: __call__ - -.. autofunction:: cubicweb.appobject.objectify_selector -.. autofunction:: cubicweb.selectors.lltrace - -Selectors __call__ should *always* return a positive integer, and shall never -return `None`. - -Useful abstract base classes for 'entity' selectors: - -.. autoclass:: cubicweb.selectors.EClassSelector -.. autoclass:: cubicweb.selectors.EntitySelector - - -Debugging -````````` - -Once in a while, one needs to understand why a view (or any AppObject) -is, or is not selected appropriately. Looking at which selectors fired -(or did not) is the way. There exists a traced_selection context -manager to help with that, *if you're running your instance in debug mode*. - -Here is an example: - -.. sourcecode:: python - - from cubicweb.selectors import traced_selection - with traced_selection(): - mycomp = self._cw.vreg['views'].select('wfhistory', self._cw, rset=rset) - -Don't forget the 'from __future__ import with_statement' at the module -top-level if you're using python 2.5. - -This will yield additional WARNINGs in the logs, like this:: - - 2009-01-09 16:43:52 - (cubicweb.selectors) WARNING: selector one_line_rset returned 0 for - -You can also give to traced_selection the registry ids of objects on which to debug -you want to debug selection ('wfhistory' in the example above). - -Also, if you're using python 2.4, which as no 'with' yet, you'll have to to it -the following way: - -.. sourcecode:: python - - from cubicweb import selectors - selectors.TRACED_OIDS = ('wfhistory',) - mycomp = self._cw.vreg['views'].select('wfhistory', self._cw, rset=rset) - selectors.TRACED_OIDS = () diff -r 0aa4d348c2e6 -r 43afbdd5c8b4 doc/book/en/development/index.rst --- a/doc/book/en/development/index.rst Fri Apr 02 16:10:17 2010 +0200 +++ b/doc/book/en/development/index.rst Fri Apr 02 16:10:35 2010 +0200 @@ -11,6 +11,7 @@ :numbered: cubes/index + vreg/index datamodel/index entityclasses/index devcore/index diff -r 0aa4d348c2e6 -r 43afbdd5c8b4 doc/book/en/development/vreg/appobject.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/book/en/development/vreg/appobject.rst Fri Apr 02 16:10:35 2010 +0200 @@ -0,0 +1,71 @@ + + +The `AppObject` class +~~~~~~~~~~~~~~~~~~~~~ + +In general: + +* we do not inherit directly from this class but from a more specific + class such as `AnyEntity`, `EntityView`, `AnyRsetView`, + `Action`... + +* to be recordable, a subclass has to define its own register (attribute + `__registry__`) and its identifier (attribute `id`). Usually we do not have + to take care of the register, only the identifier `id`. + +We can find a certain number of attributes and methods defined in this class +and common to all the application objects. + +At recording time, the following attributes are dynamically added to +the *subclasses*: + +* `vreg`, the `vregistry` of the instance +* `schema`, the instance schema +* `config`, the instance configuration + +We also find on instances, the following attributes: + +* ._cw`, `Request` instance +* `rset`, the *result set* associated to the object if necessary + +:URL handling: + * `build_url(*args, **kwargs)`, returns an absolute URL based on the + given arguments. The *controller* supposed to handle the response, + can be specified through the first positional parameter (the + connection is theoretically done automatically :). + +:Data manipulation: + + * `entity(row, col=0)`, returns the entity corresponding to the data position + in the *result set* associated to the object + + * `complete_entity(row, col=0, skip_bytes=True)`, is equivalent to `entity` but + also call the method `complete()` on the entity before returning it + +:Data formatting: + * `format_date(date, date_format=None, time=False)` returns a string for a + date time according to instance's configuration + * `format_time(time)` returns a string for a date time according to + instance's configuration + +:And more...: + + * `tal_render(template, variables)`, renders a precompiled page template with + variables in the given dictionary as context + +.. note:: + When we inherit from `AppObject` (even not directly), you *always* have to use + **super()** to get the methods and attributes of the superclasses, and not + use the class identifier. + + For example, instead of writting: :: + + class Truc(PrimaryView): + def f(self, arg1): + PrimaryView.f(self, arg1) + + You must write: :: + + class Truc(PrimaryView): + def f(self, arg1): + super(Truc, self).f(arg1) diff -r 0aa4d348c2e6 -r 43afbdd5c8b4 doc/book/en/development/vreg/selectors.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/book/en/development/vreg/selectors.rst Fri Apr 02 16:10:35 2010 +0200 @@ -0,0 +1,89 @@ +Base selectors +-------------- + +Selectors are scoring functions that are called by the registry to tell whenever +an appobject can be selected in a given context. Selector sets are for instance +the glue that tie views to the data model. Using them appropriately is an +essential part of the construction of well behaved cubes. + +Of course you may have to write your own set of selectors as your needs grows and +you get familiar with the framework (see :ref:`CustomSelectors`). + +Here is a description of generic selectors provided by CubicWeb that should suit +most of your needs. + +Bare selectors +~~~~~~~~~~~~~~ +Those selectors are somewhat dumb, which doesn't mean they're not (very) useful. + +.. autoclass:: cubicweb.appobject.yes +.. autoclass:: cubicweb.selectors.match_kwargs +.. autoclass:: cubicweb.selectors.appobject_selectable + + +Result set selectors +~~~~~~~~~~~~~~~~~~~~~ +Those selectors are looking for a result set in the context ('rset' argument or +the input context) and match or not according to its shape. Some of these +selectors have different behaviour if a particular cell of the result set is +specified using 'row' and 'col' arguments of the input context or not. + +.. autoclass:: cubicweb.selectors.none_rset +.. autoclass:: cubicweb.selectors.any_rset +.. autoclass:: cubicweb.selectors.nonempty_rset +.. autoclass:: cubicweb.selectors.empty_rset +.. autoclass:: cubicweb.selectors.one_line_rset +.. autoclass:: cubicweb.selectors.multi_lines_rset +.. autoclass:: cubicweb.selectors.multi_columns_rset +.. autoclass:: cubicweb.selectors.paginated_rset +.. autoclass:: cubicweb.selectors.sorted_rset +.. autoclass:: cubicweb.selectors.one_etype_rset +.. autoclass:: cubicweb.selectors.multi_etypes_rset + + +Entity selectors +~~~~~~~~~~~~~~~~ +Those selectors are looking for either an `entity` argument in the input context, +or entity found in the result set ('rset' argument or the input context) and +match or not according to entity's (instance or class) properties. + +.. autoclass:: cubicweb.selectors.non_final_entity +.. autoclass:: cubicweb.selectors.implements +.. autoclass:: cubicweb.selectors.score_entity +.. autoclass:: cubicweb.selectors.rql_condition +.. autoclass:: cubicweb.selectors.relation_possible +.. autoclass:: cubicweb.selectors.partial_relation_possible +.. autoclass:: cubicweb.selectors.has_related_entities +.. autoclass:: cubicweb.selectors.partial_has_related_entities +.. autoclass:: cubicweb.selectors.has_permission +.. autoclass:: cubicweb.selectors.has_add_permission + + +Logged user selectors +~~~~~~~~~~~~~~~~~~~~~ +Those selectors are looking for properties of the user issuing the request. + +.. autoclass:: cubicweb.selectors.anonymous_user +.. autoclass:: cubicweb.selectors.authenticated_user +.. autoclass:: cubicweb.selectors.match_user_groups + + +Web request selectors +~~~~~~~~~~~~~~~~~~~~~ +Those selectors are looking for properties of *web* request, they can not be +used on the data repository side. + +.. autoclass:: cubicweb.selectors.match_form_params +.. autoclass:: cubicweb.selectors.match_search_state +.. autoclass:: cubicweb.selectors.match_context_prop +.. autoclass:: cubicweb.selectors.match_view +.. autoclass:: cubicweb.selectors.primary_view +.. autoclass:: cubicweb.selectors.specified_etype_implements + + +Other selectors +~~~~~~~~~~~~~~~ +.. autoclass:: cubicweb.selectors.match_transition + +You'll also find some other (very) specific selectors hidden in other modules +than :mod:`cubicweb.selectors`. diff -r 0aa4d348c2e6 -r 43afbdd5c8b4 doc/book/en/development/vreg/vreg.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/book/en/development/vreg/vreg.rst Fri Apr 02 16:10:35 2010 +0200 @@ -0,0 +1,323 @@ +.. VRegistry: + +The `VRegistry` +--------------- + +The `VRegistry` can be seen as a two level dictionary. It contains all objects +loaded dynamically to build a |cubicweb| application. Basically: + +* first level key return a *registry*. This key corresponds to the `__registry__` + attribute of application object classes + +* second level key return a list of application objects which share the same + identifier. This key corresponds to the `__regid__` attribute of application + object classes. + +A *registry* hold a specific kind of application objects. You've for instance +a registry for entity classes, another for views, etc... + +The `VRegistry` has two main responsibilities: + +- being the access point to all registries + +- handling the registration process at startup time, and during automatic + reloading in debug mode. + + +.. _AppObjectRecording: + +Managing the recording process +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Details of the recording process +```````````````````````````````` + +.. index:: + vregistry: registration_callback + +On startup, |cubicweb| have to load application objects defined in its library +and in cubes used by the instance. Application objects from the library are +loaded first, then those provided by cubes are loaded in an ordered way (e.g. if +your cube depends on an other, objects from the dependancy will be loaded +first). Cube's modules or packages where appobject are looked at is explained in +:ref:`cubelayout`. + +For each module: + +* by default all objects are registered automatically + +* if some objects have to replace other objects, or be included only if some + condition is true, you'll have to define a `registration_callback(vreg)` + function in your module and explicitly register **all objects** in this module, + using the api defined below. + +.. Note:: + Once the function `registration_callback(vreg)` is implemented in a module, + all the objects from this module have to be explicitly registered as it + disables the automatic objects registration. + + +API for objects registration +```````````````````````````` + +Here are the registration methods that you can use in the `registration_callback` +to register your objects to the `VRegistry` instance given as argument (usually +named `vreg`): + +.. automethod:: cubicweb.cwvreg.CubicWebVRegistry.register_all +.. automethod:: cubicweb.cwvreg.CubicWebVRegistry.register_and_replace +.. automethod:: cubicweb.cwvreg.CubicWebVRegistry.register +.. automethod:: cubicweb.cwvreg.CubicWebVRegistry.register_if_interface_found +.. automethod:: cubicweb.cwvreg.CubicWebVRegistry.unregister + + +Examples +```````` +.. sourcecode:: python + + # web/views/basecomponents.py + def registration_callback(vreg): + # register everything in the module except SeeAlsoComponent + vreg.register_all(globals().values(), __name__, (SeeAlsoVComponent,)) + # conditionally register SeeAlsoVComponent + if 'see_also' in vreg.schema: + vreg.register(SeeAlsoVComponent) + +In this example, we register all application object classes defined in the module +except `SeeAlsoVComponent`. This class is then registered only if the 'see_also' +relation type is defined in the instance'schema. + +.. sourcecode:: python + + # goa/appobjects/sessions.py + def registration_callback(vreg): + vreg.register(SessionsCleaner) + # replace AuthenticationManager by GAEAuthenticationManager + vreg.register_and_replace(GAEAuthenticationManager, AuthenticationManager) + # replace PersistentSessionManager by GAEPersistentSessionManager + vreg.register_and_replace(GAEPersistentSessionManager, PersistentSessionManager) + +In this example, we explicitly register classes one by one: + +* the `SessionCleaner` class +* the `GAEAuthenticationManager` to replace the `AuthenticationManager` +* the `GAEPersistentSessionManager` to replace the `PersistentSessionManager` + +If at some point we register a new appobject class in this module, it won't be +registered at all without modification to the `registration_callback` +implementation. The previous example will register it though, thanks to the call +to the `register_all` method. + +.. _Selection: + +Runtime objects selection +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Now that we've all application objects loaded, the question is : when I want some +specific object, for instance the primary view for a given entity, how do I get +the proper object ? This is what we call the **selection mechanism**. + +As explained in the :ref:`Concepts` section: + +* each application object has a **selector**, defined by its `__select__` class attribute + +* this selector is responsible to return a **score** for a given context + + - 0 score means the object doesn't apply to this context + + - else, the higher the score, the better the object suits the context + +* the object with the higher score is selected. + +.. Note:: + + When no score is higher than the others, an exception is raised in development + mode to let you know that the engine was not able to identify the view to + apply. This error is silenced in production mode and one of the objects with + the higher score is picked. + + In such cases you would need to review your design and make sure your selectors + or appobjects are properly defined. + +For instance, if you are selecting the primary (eg `__regid__ = 'primary'`) view (eg +`__registry__ = 'views'`) for a result set containing a `Card` entity, 2 objects +will probably be selectable: + +* the default primary view (`__select__ = implements('Any')`), meaning + that the object is selectable for any kind of entity type + +* the specific `Card` primary view (`__select__ = implements('Card')`, + meaning that the object is selectable for Card entities + +Other primary views specific to other entity types won't be selectable in this +case. Among selectable objects, the implements selector will return a higher +score than the second view since it's more specific, so it will be selected as +expected. + +.. _SelectionAPI: + +API for objects selections +`````````````````````````` + +Here is the selection API you'll get on every registry. Some of them, as the +'etypes' registry, containing entity classes, extend it. In those methods, +`*args, **kwargs` is what we call the **context**. Those arguments are given to +selectors that will inspect there content and return a score accordingly. + +.. automethod:: cubicweb.vregistry.Registry.select + +.. automethod:: cubicweb.vregistry.Registry.select_or_none + +.. automethod:: cubicweb.vregistry.Registry.possible_objects + +.. automethod:: cubicweb.vregistry.Registry.object_by_id + + +.. _Selectors: + +Selectors +--------- + +Using and combining existant selectors +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can combine selectors using the `&`, `|` and `~` operators. + +When two selectors are combined using the `&` operator (formerly `chainall`), it +means that both should return a positive score. On success, the sum of scores is returned. + +When two selectors are combined using the `|` operator (former `chainfirst`), it +means that one of them should return a positive score. On success, the first +positive score is returned. + +You can also "negate" a selector by precedeing it by the `~` unary operator. + +Of course you can use parens to balance expressions. + +.. Note: + When one chains selectors, the final score is the sum of the score of each + individual selector (unless one of them returns 0, in which case the object is + non selectable) + + +Example +~~~~~~~ + +The goal: when on a Blog, one wants the RSS link to refer to blog entries, not to +the blog entity itself. + +To do that, one defines a method on entity classes that returns the RSS stream +url for a given entity. The default implementation on +:class:`~cubicweb.entities.AnyEntity` (the generic entity class used as base for +all others) and a specific implementation on Blog will do what we want. + +But when we have a result set containing several Blog entities (or different +entities), we don't know on which entity to call the aforementioned method. In +this case, we keep the generic behaviour. + +Hence we have two cases here, one for a single-entity rsets, the other for +multi-entities rsets. + +In web/views/boxes.py lies the RSSIconBox class. Look at its selector :: + + class RSSIconBox(ExtResourcesBoxTemplate): + """just display the RSS icon on uniform result set""" + __select__ = ExtResourcesBoxTemplate.__select__ & non_final_entity() + +It takes into account: + +* the inherited selection criteria (one has to look them up in the class + hierarchy to know the details) + +* :class:`~cubicweb.selectors.non_final_entity`, which filters on result sets + containing non final entities (a 'final entity' being synonym for entity + attributes type, eg `String`, `Int`, etc) + +This matches our second case. Hence we have to provide a specific component for +the first case:: + + class EntityRSSIconBox(RSSIconBox): + """just display the RSS icon on uniform result set for a single entity""" + __select__ = RSSIconBox.__select__ & one_line_rset() + +Here, one adds the :class:`~cubicweb.selectors.one_line_rset` selector, which +filters result sets of size 1. Thus, on a result set containing multiple +entities, :class:`one_line_rset` makes the EntityRSSIconBox class non +selectable. However for a result set with one entity, the `EntityRSSIconBox` +class will have a higher score than `RSSIconBox`, which is what we wanted. + +Of course, once this is done, you have to: + +* fill in the call method of `EntityRSSIconBox` + +* provide the default implementation of the method returning the RSS stream url + on :class:`~cubicweb.entities.AnyEntity` + +* redefine this method on `Blog`. + + +When to use selectors? +~~~~~~~~~~~~~~~~~~~~~~ + +Selectors are to be used whenever arises the need of dispatching on the shape or +content of a result set or whatever else context (value in request form params, +authenticated user groups, etc...). That is, almost all the time. + +.. XXX add and example of a single view w/ big "if" inside splitted into two views with appropriate selectors. + + +.. _CustomSelectors: + +Defining your own selectors +~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. autoclass:: cubicweb.appobject.Selector + :members: __call__ + +.. autofunction:: cubicweb.appobject.objectify_selector + +Selectors __call__ should *always* return a positive integer, and shall never +return `None`. + +Useful abstract base classes for 'entity' selectors: + +.. autoclass:: cubicweb.selectors.EClassSelector +.. autoclass:: cubicweb.selectors.EntitySelector + +Also, think to use the `lltrace` decorator on your selector class' :meth:`__call__` method +or below the :func:`objectify_selector` decorator of your selector function so it gets +traceable when :class:`traced_selection` is activated (see :ref:DebuggingSelectors). + +.. autofunction:: cubicweb.selectors.lltrace + + +.. _DebuggingSelectors: + +Debugging selection +~~~~~~~~~~~~~~~~~~~ + +Once in a while, one needs to understand why a view (or any AppObject) is, or is +not selected appropriately. Looking at which selectors fired (or did not) is the +way. There exists a traced_selection context manager to help with that, *if +you're running your instance in debug mode*. + +Here is an example: + +.. sourcecode:: python + + from cubicweb.selectors import traced_selection + with traced_selection(): + mycomp = self._cw.vreg['views'].select('wfhistory', self._cw, rset=rset) + +Don't forget the 'from __future__ import with_statement' at the module +top-level if you're using python 2.5. + +This will yield additional WARNINGs in the logs, like this:: + + 2009-01-09 16:43:52 - (cubicweb.selectors) WARNING: selector one_line_rset returned 0 for + +You can also give to traced_selection the registry ids of objects on which to debug +you want to debug selection ('wfhistory' in the example above). + + + +.. |cubicweb| replace:: *CubicWeb* diff -r 0aa4d348c2e6 -r 43afbdd5c8b4 doc/book/en/intro/concepts/index.rst --- a/doc/book/en/intro/concepts/index.rst Fri Apr 02 16:10:17 2010 +0200 +++ b/doc/book/en/intro/concepts/index.rst Fri Apr 02 16:10:35 2010 +0200 @@ -131,8 +131,7 @@ An `entity type` defines a set of attributes and is used in some relations. Attributes may be of the following types: `String`, `Int`, `Float`, `Boolean`, -`Date`, `Time`, `Datetime`, `Interval`, `Password`, `Bytes`, `RichString`. See -:ref:`yams.BASE_TYPES` for details. +`Date`, `Time`, `Datetime`, `Interval`, `Password`, `Bytes`, `RichString`. A `relation type` is used to define an oriented binary relation between two entity types. The left-hand part of a relation is named the `subject` and the @@ -159,8 +158,8 @@ .. _VRegistryIntro: -Registries and Objects ----------------------- +Registries and application objects +---------------------------------- Application objects ~~~~~~~~~~~~~~~~~~~ @@ -193,7 +192,8 @@ |cubicweb| provides a set of basic selectors that may be parametrized. Also, selectors can be combined with the `~` unary operator (negation) and the binary operators `&` and `|` (respectivly 'and' and 'or') to build more complex -selector. Of course complex selector may be combined too. +selector. Of course complex selector may be combined too. Last but not least, you +can write your own selectors. The `vregistry` ~~~~~~~~~~~~~~~ @@ -203,7 +203,8 @@ assigned to registries so that they can be selected dynamically while the instance is running. -In a cube, appobject classes are looked in the following modules or packages: +In a cube, application object classes are looked in the following modules or +packages: - `entities` - `views` @@ -211,43 +212,21 @@ - `hooks` -Once initialized, there are three common ways to retrieve some appobject from a -registry: +Once initialized, there are three common ways to retrieve some application object +from a registry: * get the most appropriate object by specifying an identifier. In that case, the object with the greatest score is selected. There should always be a single - appobject with a greater score than others for a particular context (see note - below). + appobject with a greater score than others for a particular context. - - If no object has a positive score, :class:`cubicweb.NoSelectableObject` - exception is raised. - -* get all appobjects applying to a context by specifying a registry. In that - case, a list of objects will be returned containing the object with the - highest score (> 0) for each identifier in that registry. +* get all objects applying to a context by specifying a registry. In that case, a + list of objects will be returned containing the object with the highest score + (> 0) for each identifier in that registry. * get the object within a particular registry/identifier. In that case no selection process is involved, the vregistry will expect to find a single object in that cell. -In all cases: - -- If no object is found for the identifier, :class:`cubicweb.ObjectNotFound` - exception is raised. - -- If you ask the `vregistry` for an unexistant registry, - :class:`cubicweb.RegistryNotFound` exception is raised. - -.. note:: - - When no score is higher than the others, an exception is raised in development - mode to let you know that the engine was not able to identify the view to - apply. This error is silenced in production mode and one of the objects with - the higher score is picked. - - In such cases you would need to review your design and make sure your selectors - or appobjects are properly defined. - .. _RQLIntro: diff -r 0aa4d348c2e6 -r 43afbdd5c8b4 selectors.py --- a/selectors.py Fri Apr 02 16:10:17 2010 +0200 +++ b/selectors.py Fri Apr 02 16:10:35 2010 +0200 @@ -74,6 +74,9 @@ print '%s -> %s for %s(%s)' % (selname, ret, vobj, vobj.__regid__) def lltrace(selector): + """use this decorator on your selectors so the becomes traceable with + :class:`traced_selection` + """ # don't wrap selectors if not in development mode if CubicWebConfiguration.mode == 'system': # XXX config.debug return selector diff -r 0aa4d348c2e6 -r 43afbdd5c8b4 vregistry.py --- a/vregistry.py Fri Apr 02 16:10:17 2010 +0200 +++ b/vregistry.py Fri Apr 02 16:10:35 2010 +0200 @@ -161,11 +161,12 @@ # dynamic selection methods ################################################ def object_by_id(self, oid, *args, **kwargs): - """return object with the given oid. Only one object is expected to be - found. + """return object with the `oid` identifier. Only one object is expected + to be found. - raise `ObjectNotFound` if not object with id in - raise `AssertionError` if there is more than one object there + raise :exc:`ObjectNotFound` if not object with id in + + raise :exc:`AssertionError` if there is more than one object there """ objects = self[oid] assert len(objects) == 1, objects @@ -175,8 +176,9 @@ """return the most specific object among those with the given oid according to the given context. - raise `ObjectNotFound` if not object with id in - raise `NoSelectableObject` if not object apply + raise :exc:`ObjectNotFound` if not object with id in + + raise :exc:`NoSelectableObject` if not object apply """ return self._select_best(self[oid], *args, **kwargs) @@ -318,6 +320,20 @@ # self[regname].pop(oid, None) def register_all(self, objects, modname, butclasses=()): + """register all `objects` given. Objects which are not from the module + `modname` or which are in `butclasses` won't be registered. + + Typical usage is: + + .. sourcecode:: python + + vreg.register_all(globals().values(), __name__, (ClassIWantToRegisterExplicitly,)) + + So you get partially automatic registration, keeping manual registration + for some object (to use + :meth:`~cubicweb.cwvreg.CubicWebRegistry.register_and_replace` for + instance) + """ for obj in objects: try: if obj.__module__ != modname or obj in butclasses: @@ -329,7 +345,13 @@ self.register(obj, oid=oid) def register(self, obj, registryname=None, oid=None, clear=False): - """base method to add an object in the registry""" + """register `obj` application object into `registryname` or + `obj.__registry__` if not specified, with identifier `oid` or + `obj.__regid__` if not specified. + + If `clear` is true, all objects with the same identifier will be + previously unregistered. + """ assert not '__abstract__' in obj.__dict__ try: vname = obj.__name__ @@ -344,10 +366,18 @@ self._loadedmods[obj.__module__][classid(obj)] = obj def unregister(self, obj, registryname=None): + """unregister `obj` application object from the registry `registryname` or + `obj.__registry__` if not specified. + """ for registryname in class_registries(obj, registryname): self[registryname].unregister(obj) def register_and_replace(self, obj, replaced, registryname=None): + """register `obj` application object into `registryname` or + `obj.__registry__` if not specified. If found, the `replaced` object + will be unregistered first (else a warning will be issued as it's + generally unexpected). + """ for registryname in class_registries(obj, registryname): self[registryname].register_and_replace(obj, replaced)