cubicweb logo

Table Of Contents

Previous topic

1.3. Available cubes

Next topic

3. Data model

This Page

2. The VRegistry, selectors and application objects

This chapter deals with some of the core concepts of the CubicWeb framework which make it different from other frameworks (and maybe not easy to grasp at a first glance). To be able to do advanced development with CubicWeb you need a good understanding of what is explained 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.

An overview of AppObjects, the VRegistry and Selectors is given in the Registries and application objects chapter.

2.1. The VRegistry

The VRegistry can be seen as a two-level dictionary. It contains all dynamically loaded objects (subclasses of The AppObject class) to build a CubicWeb application. Basically:

  • the first level key returns a registry. This key corresponds to the __registry__ attribute of application object classes
  • the second level key returns a list of application objects which share the same identifier. This key corresponds to the __regid__ attribute of application object classes.

A registry holds a specific kind of application objects. There is 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.

2.1.1. Details of the recording process

On startup, CubicWeb loads 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 dependency order (e.g. if your cube depends on an other, objects from the dependency will be loaded first). The layout of the modules or packages in a cube is explained in Standard structure for a cube.

For each module:

  • by default all objects are registered automatically
  • if some objects have to replace other objects, or have to be included only if some condition is met, 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.

2.1.2. 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):

CubicWebVRegistry.register_all(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:

vreg.register_all(globals().values(), __name__, (ClassIWantToRegisterExplicitly,))

So you get partially automatic registration, keeping manual registration for some object (to use register_and_replace() for instance)

CubicWebVRegistry.register_and_replace(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).
CubicWebVRegistry.register(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.

CubicWebVRegistry.unregister(obj, registryname=None)
unregister obj application object from the registry registryname or obj.__registry__ if not specified.

Examples:

# 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.

# 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.

2.1.3. Runtime objects selection

Now that we have 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 The Core Concepts of CubicWeb 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 highest score is selected.

Note

When no single object has the highest score, 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 highest score is picked.

In such cases you would need to review your design and make sure your selectors or appobjects are properly defined. Such an error is typically caused by either forgetting to change the __regid__ in a derived class, or by having copy-pasted some code.

For instance, if you are selecting the primary (__regid__ = ‘primary’) view (__registry__ = ‘views’) for a result set containing a Card entity, two objects will probably be selectable:

  • the default primary view (__select__ = is_instance(‘Any’)), meaning that the object is selectable for any kind of entity type
  • the specific Card primary view (__select__ = is_instance(‘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 is_instance(‘Card’) selector will return a higher score since it’s more specific, so the correct view will be selected as expected.

2.1.4. 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 their content and return a score accordingly.

Registry.select(_Registry__oid, *args, **kwargs)

return the most specific object among those with the given oid according to the given context.

raise ObjectNotFound if not object with id <oid> in <registry>

raise NoSelectableObject if not object apply

Registry.select_or_none(_Registry__oid, *args, **kwargs)
return the most specific object among those with the given oid according to the given context, or None if no object applies.
Registry.possible_objects(*args, **kwargs)
return an iterator on possible objects in this registry for the given context
Registry.object_by_id(oid, *args, **kwargs)

return object with the oid identifier. Only one object is expected to be found.

raise ObjectNotFound if not object with id <oid> in <registry>

raise AssertionError if there is more than one object there

2.2. The AppObject class

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.

class cubicweb.appobject.AppObject(req, **extra)

This is the base class for CubicWeb application objects which are selected according to a context (usually at least a request and a result set).

The following attributes should be set on concret appobject classes:

__registry__
name of the registry for this object (string like ‘views’, ‘templates’...)
__regid__
object’s identifier in the registry (string like ‘main’, ‘primary’, ‘folder_box’)
__select__
class’selector

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
current request
cw_extra_kwargs
other received arguments

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
context result set or None
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
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)
    

2.3. 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 CustomSelectors).

Here is a description of generic selectors provided by CubicWeb that should suit most of your needs.

2.3.1. Bare selectors

Those selectors are somewhat dumb, which doesn’t mean they’re not (very) useful.

class cubicweb.appobject.yes(score=0.5)

Return the score given as parameter, with a default score of 0.5 so any other selector take precedence.

Usually used for appobjects which can be selected whatever the context, or also sometimes to add arbitrary points to a score.

Take care, yes(0) could be named ‘no’...

2.3.2. 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.

2.3.3. 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.

2.3.4. Logged user selectors

Those selectors are looking for properties of the user issuing the request.

2.3.5. Web request selectors

Those selectors are looking for properties of web request, they can not be used on the data repository side.

2.3.6. Other selectors

You’ll also find some other (very) specific selectors hidden in other modules than cubicweb.selectors.