# HG changeset patch # User Sylvain Thénault # Date 1270541500 -7200 # Node ID 70181998897fb051da7fe21aea48f7e72cb08836 # Parent fe56baf63ecbb0bcb97b7b1c6bc512462c77e093 more / cleaner / in code documentation of vreg, selectors and appobject diff -r fe56baf63ecb -r 70181998897f appobject.py --- a/appobject.py Tue Apr 06 10:10:47 2010 +0200 +++ b/appobject.py Tue Apr 06 10:11:40 2010 +0200 @@ -1,11 +1,18 @@ -"""Base class for dynamically loaded objects accessible through the vregistry. - -You'll also find some convenience classes to build selectors. +# :organization: Logilab +# :copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. +# :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr +# :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses +""" +The `AppObject` class +--------------------- -:organization: Logilab -:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. -:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr -:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses +The AppObject class is the base class for all dynamically loaded objects +(application objects) accessible through the vregistry. + +We can find a certain number of attributes and methods defined in this class and +common to all the application objects. + +.. autoclass:: AppObject """ __docformat__ = "restructuredtext en" @@ -21,13 +28,17 @@ # selector base classes and operations ######################################## def objectify_selector(selector_func): - """convenience decorator for simple selectors where a class definition - would be overkill:: + """Most of the time, a simple score function is enough to build a selector. + The :func:`objectify_selector` decorator turn it into a proper selector + class:: @objectify_selector def one(cls, *args, **kwargs): return 1 + class MyView(View): + __select__ = View.__select__ & one() + """ return type(selector_func.__name__, (Selector,), {'__doc__': selector_func.__doc__, @@ -49,7 +60,7 @@ class Selector(object): """base class for selector classes providing implementation - for operators ``&`` and ``|`` + for operators ``&``, ``|`` and ``~`` This class is only here to give access to binary operators, the selector logic itself should be implemented in the __call__ method @@ -205,42 +216,77 @@ selected according to a context (usually at least a request and a result set). - Concrete application objects classes are designed to be loaded by the - vregistry and should be accessed through it, not by direct instantiation. + The following attributes should be set on concret appobject classes: - The following attributes should be set on concret appobject classes: - :__registry__: + :attr:`__registry__` name of the registry for this object (string like 'views', 'templates'...) - :__regid__: + + :attr:`__regid__` object's identifier in the registry (string like 'main', 'primary', 'folder_box') - :__select__: + + :attr:`__select__` class'selector - Moreover, the `__abstract__` attribute may be set to True to indicate - that a appobject is abstract and should not be registered. + Moreover, the `__abstract__` attribute may be set to True to indicate that a + class is abstract and should not be registered. At selection time, the following attributes are set on the instance: - :_cw: + :attr:`_cw` current request - :cw_extra_kwargs: + :attr:`cw_extra_kwargs` other received arguments - only if rset is found in arguments (in which case rset/row/col will be - removed from cwextra_kwargs): + And also the following, only if `rset` is found in arguments (in which case + rset/row/col will be removed from `cwextra_kwargs`): - :cw_rset: + :attr:`cw_rset` context result set or None - :cw_row: + + :attr:`cw_row` if a result set is set and the context is about a particular cell in the result set, and not the result set as a whole, specify the row number we are interested in, else None - :cw_col: + + :attr:`cw_col` if a result set is set and the context is about a particular cell in the result set, and not the result set as a whole, specify the col number we are interested in, else None + + + .. Note:: + + * 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 registry (attribute + `__registry__`) and its identifier (attribute `__regid__`). Usually + you don't have to take care of the registry since it's set by the base + class, only the identifier `id` + + * application objects are designed to be loaded by the vregistry and + should be accessed through it, not by direct instantiation, besides + to use it as base classe. + + + * 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) + """ __registry__ = None __regid__ = None diff -r fe56baf63ecb -r 70181998897f cwvreg.py --- a/cwvreg.py Tue Apr 06 10:10:47 2010 +0200 +++ b/cwvreg.py Tue Apr 06 10:11:40 2010 +0200 @@ -1,9 +1,178 @@ -"""extend the generic VRegistry with some cubicweb specific stuff +# :organization: Logilab +# :copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. +# :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr +# :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses +""".. 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: + +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) -:organization: Logilab -:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. -:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr -:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses +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 """ __docformat__ = "restructuredtext en" _ = unicode diff -r fe56baf63ecb -r 70181998897f doc/book/en/development/index.rst --- a/doc/book/en/development/index.rst Tue Apr 06 10:10:47 2010 +0200 +++ b/doc/book/en/development/index.rst Tue Apr 06 10:11:40 2010 +0200 @@ -11,7 +11,7 @@ :numbered: cubes/index - vreg/index + vreg.rst datamodel/index entityclasses/index devcore/index diff -r fe56baf63ecb -r 70181998897f doc/book/en/development/vreg.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/book/en/development/vreg.rst Tue Apr 06 10:11:40 2010 +0200 @@ -0,0 +1,110 @@ +The VRegistry, selectors and application objects +================================================ + +This chapter talk about core concepts of the |cubicweb| framework, that make it +different from other framework (and probably not easy to grasp at a first +glance). You won't be able to do advanced development with |cubicweb| without +a good understanding of what's explain below. + +This chapter goes deep into details. You don't have to remember them all but keep +it in mind so you can go back there later... + +.. toctree:: + :maxdepth: 1 + +.. autodocstring:: cubicweb.cwvreg +.. autodocstring:: cubicweb.selectors +.. automodule:: cubicweb.appobject + +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`. + + +.. |cubicweb| replace:: *CubicWeb* diff -r fe56baf63ecb -r 70181998897f doc/book/en/development/vreg/appobject.rst --- a/doc/book/en/development/vreg/appobject.rst Tue Apr 06 10:10:47 2010 +0200 +++ b/doc/book/en/development/vreg/appobject.rst Tue Apr 06 10:11:40 2010 +0200 @@ -1,32 +1,7 @@ - - -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 +XXX todo: +* configure members for doc generated for appojbect class, +* configure module's member into the module +* put doc below somewhere else :URL handling: * `build_url(*args, **kwargs)`, returns an absolute URL based on the @@ -52,20 +27,3 @@ * `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 fe56baf63ecb -r 70181998897f doc/book/en/development/vreg/selectors.rst --- a/doc/book/en/development/vreg/selectors.rst Tue Apr 06 10:10:47 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 fe56baf63ecb -r 70181998897f doc/book/en/development/vreg/vreg.rst --- a/doc/book/en/development/vreg/vreg.rst Tue Apr 06 10:10:47 2010 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,323 +0,0 @@ -.. 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 fe56baf63ecb -r 70181998897f selectors.py --- a/selectors.py Tue Apr 06 10:10:47 2010 +0200 +++ b/selectors.py Tue Apr 06 10:11:40 2010 +0200 @@ -1,42 +1,184 @@ -"""This file contains some basic selectors required by application objects. +# :organization: Logilab +# :copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. +# :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr +# :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses +""".. _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. -A selector is responsible to score how well an object may be used with a -given context by returning a score. +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: + +.. sourcecode:: python + + 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) -In CubicWeb Usually the context consists for a request object, a result set -or None, a specific row/col in the result set, etc... +This matches our second case. Hence we have to provide a specific component for +the first case: + +.. sourcecode:: python + + 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`. -If you have trouble with selectors, especially if the objet (typically -a view or a component) you want to use is not selected and you want to -know which one(s) of its selectors fail (e.g. returns 0), you can use -`traced_selection` or even direclty `TRACED_OIDS`. +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. + +Here is a quick example: + +.. sourcecode:: python + + class UserLink(component.Component): + '''if the user is the anonymous user, build a link to login else a link + to the connected user object with a loggout link + ''' + __regid__ = 'loggeduserlink' -`TRACED_OIDS` is a tuple of traced object ids. The special value -'all' may be used to log selectors for all objects. + def call(self): + if self._cw.cnx.anonymous_connection: + # display login link + ... + else: + # display a link to the connected user object with a loggout link + ... + +The proper way to implement this with |cubicweb| is two have two different +classes sharing the same identifier but with different selectors so you'll get +the correct one according to the context: + -For instance, say that the following code yields a `NoSelectableObject` -exception:: + class UserLink(component.Component): + '''display a link to the connected user object with a loggout link''' + __regid__ = 'loggeduserlink' + __select__ = component.Component.__select__ & authenticated_user() - self.view('calendar', myrset) + def call(self): + # display useractions and siteactions + ... + + class AnonUserLink(component.Component): + '''build a link to login''' + __regid__ = 'loggeduserlink' + __select__ = component.Component.__select__ & anonymous_user() -You can log the selectors involved for *calendar* by replacing the line -above by:: + def call(self): + # display login link + ... + +The big advantage, aside readibily once you're familiar with the system, is that +your cube becomes much more easily customizable by improving componentization. + + +.. _CustomSelectors: - from cubicweb.selectors import traced_selection - with traced_selection(): - self.view('calendar', myrset) +Defining your own selectors +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. autodocstring:: cubicweb.appobject::objectify_selector -With python 2.5, think to add: +In other case, you can take a look at the following abstract base classes: + +.. autoclass:: cubicweb.selectors.ExpectedValueSelector +.. autoclass:: cubicweb.selectors.EClassSelector +.. autoclass:: cubicweb.selectors.EntitySelector - from __future__ import with_statement +Also, think to use the :func:`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 -at the top of your module. +.. Note:: + Selectors __call__ should *always* return a positive integer, and shall never + return `None`. + + +.. _DebuggingSelectors: -:organization: Logilab -:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. -:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr -:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses +Debugging selection +~~~~~~~~~~~~~~~~~~~ + +Once in a while, one needs to understand why a view (or any application object) +is, or is not selected appropriately. Looking at which selectors fired (or did +not) is the way. The :class:`cubicweb.selectors.traced_selection` context +manager to help with that, *if you're running your instance in debug mode*. + +.. autoclass:: cubicweb.selectors.traced_selection + + +.. |cubicweb| replace:: *CubicWeb* """ __docformat__ = "restructuredtext en" @@ -90,19 +232,31 @@ return traced class traced_selection(object): - """selector debugging helper. - + """ Typical usage is : - >>> with traced_selection(): - ... # some code in which you want to debug selectors - ... # for all objects + .. sourcecode:: python + + >>> from cubicweb.selectors import traced_selection + >>> with traced_selection(): + ... # some code in which you want to debug selectors + ... # for all objects + + Don't forget the 'from __future__ import with_statement' at the module top-level + if you're using python prior to 2.6. - or + This will yield lines like this in the logs:: + + selector one_line_rset returned 0 for - >>> with traced_selection( ('oid1', 'oid2') ): - ... # some code in which you want to debug selectors - ... # for objects with id 'oid1' and 'oid2' + You can also give to :class:`traced_selection` the identifiers of objects on + which you want to debug selection ('oid1' and 'oid2' in the example above). + + .. sourcecode:: python + + >>> with traced_selection( ('oid1', 'oid2') ): + ... # some code in which you want to debug selectors + ... # for objects with id 'oid1' and 'oid2' """ def __init__(self, traced='all'): @@ -264,12 +418,12 @@ - `accept_none` is False and some cell in the column has a None value (this may occurs with outer join) - .. note:: - using EntitySelector or EClassSelector as base selector class impacts - performance, since when no entity or row is specified the later works on - every different *entity class* found in the result set, while the former - works on each *entity* (eg each row of the result set), which may be much - more costly. + .. Note:: + using :class:`EntitySelector` or :class:`EClassSelector` as base selector + class impacts performance, since when no entity or row is specified the + later works on every different *entity class* found in the result set, + while the former works on each *entity* (eg each row of the result set), + which may be much more costly. """ @lltrace @@ -310,8 +464,12 @@ class ExpectedValueSelector(Selector): - """Take a list of expected values as initializer argument, check - _get_value method return one of these expected values. + """Take a list of expected values as initializer argument and store them + into the :attr:`expected` set attribute. + + You should implements the :meth:`_get_value(cls, req, **kwargs)` method + which should return the value for the given context. The selector will then + return 1 if the value is expected, else 0. """ def __init__(self, *expected): assert expected, self @@ -349,10 +507,12 @@ class appobject_selectable(Selector): - """return 1 if another appobject is selectable using the same input context. + """Return 1 if another appobject is selectable using the same input context. Initializer arguments: + * `registry`, a registry name + * `regid`, an object identifier in this registry """ def __init__(self, registry, regid):