# HG changeset patch # User Sylvain Thénault <sylvain.thenault@logilab.fr> # Date 1249305407 -7200 # Node ID de974465d381edd7830a33553a5f87b8a0013989 # Parent a93ae0f6c0adda96045d4235058b698c1be257d2 [appobject] kill VObject class, move base selector classes to appobject diff -r a93ae0f6c0ad -r de974465d381 appobject.py --- a/appobject.py Mon Aug 03 14:14:07 2009 +0200 +++ b/appobject.py Mon Aug 03 15:16:47 2009 +0200 @@ -1,4 +1,6 @@ -"""Base class for dynamically loaded objects manipulated in the web interface +"""Base class for dynamically loaded objects accessible through the vregistry. + +You'll also find some convenience classes to build selectors. :organization: Logilab :copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. @@ -7,20 +9,23 @@ """ __docformat__ = "restructuredtext en" +import types +from logging import getLogger from datetime import datetime, timedelta, time from logilab.common.decorators import classproperty from logilab.common.deprecation import deprecated +from logilab.common.logging_ext import set_log_methods from rql.nodes import VariableRef, SubQuery from rql.stmts import Union, Select from cubicweb import Unauthorized, NoSelectableObject -from cubicweb.vregistry import VObject, AndSelector -from cubicweb.selectors import yes from cubicweb.utils import UStringIO, ustrftime, strptime, todate, todatetime ONESECOND = timedelta(0, 1, 0) +CACHE_REGISTRY = {} + class Cache(dict): def __init__(self): @@ -29,14 +34,200 @@ self.cache_creation_date = _now self.latest_cache_lookup = _now -CACHE_REGISTRY = {} + +# selector base classes and operations ######################################## + +def objectify_selector(selector_func): + """convenience decorator for simple selectors where a class definition + would be overkill:: + + @objectify_selector + def yes(cls, *args, **kwargs): + return 1 + + """ + return type(selector_func.__name__, (Selector,), + {'__call__': lambda self, *args, **kwargs: selector_func(*args, **kwargs)}) + + +def _instantiate_selector(selector): + """ensures `selector` is a `Selector` instance + + NOTE: This should only be used locally in build___select__() + XXX: then, why not do it ?? + """ + if isinstance(selector, types.FunctionType): + return objectify_selector(selector)() + if isinstance(selector, type) and issubclass(selector, Selector): + return selector() + return selector + + +class Selector(object): + """base class for selector classes providing implementation + 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 + + + a selector is called to help choosing the correct object for a + particular context by returning a score (`int`) telling how well + the class given as first argument apply to the given context. + + 0 score means that the class doesn't apply. + """ + + @property + def func_name(self): + # backward compatibility + return self.__class__.__name__ + + def search_selector(self, selector): + """search for the given selector or selector instance in the selectors + tree. Return it of None if not found + """ + if self is selector: + return self + if isinstance(selector, type) and isinstance(self, selector): + return self + return None + + def __str__(self): + return self.__class__.__name__ + + def __and__(self, other): + return AndSelector(self, other) + def __rand__(self, other): + return AndSelector(other, self) + + def __or__(self, other): + return OrSelector(self, other) + def __ror__(self, other): + return OrSelector(other, self) + + def __invert__(self): + return NotSelector(self) + + # XXX (function | function) or (function & function) not managed yet + + def __call__(self, cls, *args, **kwargs): + return NotImplementedError("selector %s must implement its logic " + "in its __call__ method" % self.__class__) + + +class MultiSelector(Selector): + """base class for compound selector classes""" + + def __init__(self, *selectors): + self.selectors = self.merge_selectors(selectors) + + def __str__(self): + return '%s(%s)' % (self.__class__.__name__, + ','.join(str(s) for s in self.selectors)) + + @classmethod + def merge_selectors(cls, selectors): + """deal with selector instanciation when necessary and merge + multi-selectors if possible: -class AppObject(VObject): - """This is the base class for CubicWeb application objects - which are selected according to a request and result set. + AndSelector(AndSelector(sel1, sel2), AndSelector(sel3, sel4)) + ==> AndSelector(sel1, sel2, sel3, sel4) + """ + merged_selectors = [] + for selector in selectors: + try: + selector = _instantiate_selector(selector) + except: + pass + #assert isinstance(selector, Selector), selector + if isinstance(selector, cls): + merged_selectors += selector.selectors + else: + merged_selectors.append(selector) + return merged_selectors + + def search_selector(self, selector): + """search for the given selector or selector instance in the selectors + tree. Return it of None if not found + """ + for childselector in self.selectors: + if childselector is selector: + return childselector + found = childselector.search_selector(selector) + if found is not None: + return found + return None + + +class AndSelector(MultiSelector): + """and-chained selectors (formerly known as chainall)""" + def __call__(self, cls, *args, **kwargs): + score = 0 + for selector in self.selectors: + partscore = selector(cls, *args, **kwargs) + if not partscore: + return 0 + score += partscore + return score + - Classes are kept in the vregistry and instantiation is done at selection - time. +class OrSelector(MultiSelector): + """or-chained selectors (formerly known as chainfirst)""" + def __call__(self, cls, *args, **kwargs): + for selector in self.selectors: + partscore = selector(cls, *args, **kwargs) + if partscore: + return partscore + return 0 + +class NotSelector(Selector): + """negation selector""" + def __init__(self, selector): + self.selector = selector + + def __call__(self, cls, *args, **kwargs): + score = self.selector(cls, *args, **kwargs) + return int(not score) + + def __str__(self): + return 'NOT(%s)' % super(NotSelector, self).__str__() + + +class yes(Selector): + """return arbitrary score + + default score of 0.5 so any other selector take precedence + """ + def __init__(self, score=0.5): + self.score = score + + def __call__(self, *args, **kwargs): + return self.score + + +# the base class for all appobjects ############################################ + +class AppObject(object): + """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). + + 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: + :__registry__: + name of the registry for this object (string like 'views', + 'templates'...) + :id: + 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 appobject is abstract and should not be registered. At registration time, the following attributes are set on the class: :vreg: @@ -46,20 +237,64 @@ :config: the instance's configuration - At instantiation time, the following attributes are set on the instance: + At selection time, the following attributes are set on the instance: :req: current request :rset: - result set on which the object is applied + context result set or None + :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 + :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 """ + __registry__ = None + id = None __select__ = yes() @classmethod - def registered(cls, reg): - super(AppObject, cls).registered(reg) - cls.vreg = reg.vreg - cls.schema = reg.schema - cls.config = reg.config + def classid(cls): + """returns a unique identifier for the appobject""" + return '%s.%s' % (cls.__module__, cls.__name__) + + # XXX bw compat code + @classmethod + def build___select__(cls): + for klass in cls.mro(): + if klass.__name__ == 'AppObject': + continue # the bw compat __selector__ is there + klassdict = klass.__dict__ + if ('__select__' in klassdict and '__selectors__' in klassdict + and '__selgenerated__' not in klassdict): + raise TypeError("__select__ and __selectors__ can't be used together on class %s" % cls) + if '__selectors__' in klassdict and '__selgenerated__' not in klassdict: + cls.__selgenerated__ = True + # case where __selectors__ is defined locally (but __select__ + # is in a parent class) + selectors = klassdict['__selectors__'] + if len(selectors) == 1: + # micro optimization: don't bother with AndSelector if there's + # only one selector + select = _instantiate_selector(selectors[0]) + else: + select = AndSelector(*selectors) + cls.__select__ = select + + @classmethod + def registered(cls, registry): + """called by the registry when the appobject has been registered. + + It must return the object that will be actually registered (this may be + the right hook to create an instance for example). By default the + appobject is returned without any transformation. + """ + cls.build___select__() + cls.vreg = registry.vreg + cls.schema = registry.schema + cls.config = registry.config cls.register_properties() return cls @@ -69,9 +304,13 @@ @classmethod def selected(cls, *args, **kwargs): - """by default web app objects are usually instantiated on - selection according to a request, a result set, and optional - row and col + """called by the registry when the appobject has been selected. + + It must return the object that will be actually returned by the .select + method (this may be the right hook to create an instance for + example). By default the selected object is called using the given args + and kwargs and the resulting value (usually a class instance) is + returned without any transformation. """ return cls(*args, **kwargs) @@ -340,3 +579,5 @@ first = rql.split(' ', 1)[0].lower() if first in ('insert', 'set', 'delete'): raise Unauthorized(self.req._('only select queries are authorized')) + +set_log_methods(AppObject, getLogger('cubicweb.appobject')) diff -r a93ae0f6c0ad -r de974465d381 common/test/unittest_migration.py --- a/common/test/unittest_migration.py Mon Aug 03 14:14:07 2009 +0200 +++ b/common/test/unittest_migration.py Mon Aug 03 15:16:47 2009 +0200 @@ -37,8 +37,8 @@ self.config = MigrTestConfig('data') from yams.schema import Schema self.config.load_schema = lambda expand_cubes=False: Schema('test') - self.config.__class__.cubicweb_vobject_path = frozenset() - self.config.__class__.cube_vobject_path = frozenset() + self.config.__class__.cubicweb_appobject_path = frozenset() + self.config.__class__.cube_appobject_path = frozenset() def test_filter_scripts_base(self): self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,3,0), (2,4,0)), diff -r a93ae0f6c0ad -r de974465d381 cwconfig.py --- a/cwconfig.py Mon Aug 03 14:14:07 2009 +0200 +++ b/cwconfig.py Mon Aug 03 15:16:47 2009 +0200 @@ -142,7 +142,7 @@ name = None # log messages format (see logging module documentation for available keys) log_format = '%(asctime)s - (%(name)s) %(levelname)s: %(message)s' - # nor remove vobjects based on unused interface + # nor remove appobjects based on unused interface cleanup_interface_sobjects = True if os.environ.get('APYCOT_ROOT'): @@ -419,8 +419,8 @@ except Exception, ex: cls.warning("can't init cube %s: %s", cube, ex) - cubicweb_vobject_path = set(['entities']) - cube_vobject_path = set(['entities']) + cubicweb_appobject_path = set(['entities']) + cube_appobject_path = set(['entities']) @classmethod def build_vregistry_path(cls, templpath, evobjpath=None, tvobjpath=None): @@ -430,13 +430,13 @@ :param evobjpath: optional list of sub-directories (or files without the .py ext) of the cubicweb library that should be tested and added to the output list - if they exists. If not give, default to `cubicweb_vobject_path` class + if they exists. If not give, default to `cubicweb_appobject_path` class attribute. :param tvobjpath: optional list of sub-directories (or files without the .py ext) of directories given in `templpath` that should be tested and added to the output list if they exists. If not give, default to - `cube_vobject_path` class attribute. + `cube_appobject_path` class attribute. """ vregpath = cls.build_vregistry_cubicweb_path(evobjpath) vregpath += cls.build_vregistry_cube_path(templpath, tvobjpath) @@ -446,7 +446,7 @@ def build_vregistry_cubicweb_path(cls, evobjpath=None): vregpath = [] if evobjpath is None: - evobjpath = cls.cubicweb_vobject_path + evobjpath = cls.cubicweb_appobject_path for subdir in evobjpath: path = join(CW_SOFTWARE_ROOT, subdir) if exists(path): @@ -457,7 +457,7 @@ def build_vregistry_cube_path(cls, templpath, tvobjpath=None): vregpath = [] if tvobjpath is None: - tvobjpath = cls.cube_vobject_path + tvobjpath = cls.cube_appobject_path for directory in templpath: for subdir in tvobjpath: path = join(directory, subdir) diff -r a93ae0f6c0ad -r de974465d381 cwvreg.py --- a/cwvreg.py Mon Aug 03 14:14:07 2009 +0200 +++ b/cwvreg.py Mon Aug 03 15:16:47 2009 +0200 @@ -34,7 +34,7 @@ if impl: return sorted(impl.expected_ifaces) except AttributeError: - pass # old-style vobject classes with no accepts_interfaces + pass # old-style appobject classes with no accepts_interfaces except: print 'bad selector %s on %s' % (obj.__select__, obj) raise @@ -308,7 +308,7 @@ # we may want to keep interface dependent objects (e.g.for i18n # catalog generation) if self.config.cleanup_interface_sobjects: - # remove vobjects that don't support any available interface + # remove appobjects that don't support any available interface implemented_interfaces = set() if 'Any' in self.get('etypes', ()): for etype in self.schema.entities(): @@ -323,7 +323,7 @@ or iface for iface in ifaces) if not ('Any' in ifaces or ifaces & implemented_interfaces): - self.debug('kicking vobject %s (no implemented ' + self.debug('kicking appobject %s (no implemented ' 'interface among %s)', obj, ifaces) self.unregister(obj) # clear needs_iface so we don't try to remove some not-anymore-in diff -r a93ae0f6c0ad -r de974465d381 dbapi.py --- a/dbapi.py Mon Aug 03 14:14:07 2009 +0200 +++ b/dbapi.py Mon Aug 03 15:16:47 2009 +0200 @@ -17,6 +17,7 @@ from logilab.common.logging_ext import set_log_methods from logilab.common.decorators import monkeypatch +from logilab.common.deprecation import deprecated from cubicweb import ETYPE_NAME_MAP, ConnectionError, RequestSessionMixIn from cubicweb import cwvreg, cwconfig @@ -29,10 +30,10 @@ except KeyError: return '' -def _fix_cls_attrs(reg, vobject): - vobject.vreg = reg.vreg - vobject.schema = reg.schema - vobject.config = reg.config +def _fix_cls_attrs(reg, appobject): + appobject.vreg = reg.vreg + appobject.schema = reg.schema + appobject.config = reg.config def multiple_connections_fix(): """some monkey patching necessary when an application has to deal with @@ -43,15 +44,15 @@ defaultcls = cwvreg.VRegistry.REGISTRY_FACTORY[None] orig_select_best = defaultcls.orig_select_best = defaultcls.select_best @monkeypatch(defaultcls) - def select_best(self, vobjects, *args, **kwargs): + def select_best(self, appobjects, *args, **kwargs): """return an instance of the most specific object according to parameters raise NoSelectableObject if no object apply """ - for vobjectcls in vobjects: - _fix_cls_attrs(self, vobjectcls) - selected = orig_select_best(self, vobjects, *args, **kwargs) + for appobjectcls in appobjects: + _fix_cls_attrs(self, appobjectcls) + selected = orig_select_best(self, appobjects, *args, **kwargs) # redo the same thing on the instance so it won't use equivalent class # attributes (which may change) _fix_cls_attrs(self, selected) @@ -448,7 +449,7 @@ raise ProgrammingError('Closed connection') return self._repo.get_schema() - def load_vobjects(self, cubes=_MARKER, subpath=None, expand=True, + def load_appobjects(self, cubes=_MARKER, subpath=None, expand=True, force_reload=None): config = self.vreg.config if cubes is _MARKER: @@ -481,11 +482,13 @@ if self._repo.config.instance_hooks: hm.register_hooks(config.load_hooks(self.vreg)) + load_vobjects = deprecated()(load_appobjects) + def use_web_compatible_requests(self, baseurl, sitetitle=None): """monkey patch DBAPIRequest to fake a cw.web.request, so you should able to call html views using rset from a simple dbapi connection. - You should call `load_vobjects` at some point to register those views. + You should call `load_appobjects` at some point to register those views. """ from cubicweb.web.request import CubicWebRequestBase as cwrb DBAPIRequest.build_ajax_replace_url = cwrb.build_ajax_replace_url.im_func diff -r a93ae0f6c0ad -r de974465d381 devtools/__init__.py --- a/devtools/__init__.py Mon Aug 03 14:14:07 2009 +0200 +++ b/devtools/__init__.py Mon Aug 03 15:16:47 2009 +0200 @@ -142,8 +142,8 @@ class BaseApptestConfiguration(TestServerConfiguration, TwistedConfiguration): repo_method = 'inmemory' options = merge_options(TestServerConfiguration.options + TwistedConfiguration.options) - cubicweb_vobject_path = TestServerConfiguration.cubicweb_vobject_path | TwistedConfiguration.cubicweb_vobject_path - cube_vobject_path = TestServerConfiguration.cube_vobject_path | TwistedConfiguration.cube_vobject_path + cubicweb_appobject_path = TestServerConfiguration.cubicweb_appobject_path | TwistedConfiguration.cubicweb_appobject_path + cube_appobject_path = TestServerConfiguration.cube_appobject_path | TwistedConfiguration.cube_appobject_path def available_languages(self, *args): return ('en', 'fr', 'de') diff -r a93ae0f6c0ad -r de974465d381 devtools/devctl.py --- a/devtools/devctl.py Mon Aug 03 14:14:07 2009 +0200 +++ b/devtools/devctl.py Mon Aug 03 15:16:47 2009 +0200 @@ -29,8 +29,8 @@ class DevCubeConfiguration(ServerConfiguration, WebConfiguration): """dummy config to get full library schema and entities""" creating = True - cubicweb_vobject_path = ServerConfiguration.cubicweb_vobject_path | WebConfiguration.cubicweb_vobject_path - cube_vobject_path = ServerConfiguration.cube_vobject_path | WebConfiguration.cube_vobject_path + cubicweb_appobject_path = ServerConfiguration.cubicweb_appobject_path | WebConfiguration.cubicweb_appobject_path + cube_appobject_path = ServerConfiguration.cube_appobject_path | WebConfiguration.cube_appobject_path def __init__(self, cube): super(DevCubeConfiguration, self).__init__(cube) diff -r a93ae0f6c0ad -r de974465d381 devtools/testlib.py --- a/devtools/testlib.py Mon Aug 03 14:14:07 2009 +0200 +++ b/devtools/testlib.py Mon Aug 03 15:16:47 2009 +0200 @@ -377,9 +377,9 @@ rset2 = rset.limit(limit=1, offset=row) yield rset2 -def not_selected(vreg, vobject): +def not_selected(vreg, appobject): try: - vreg._selected[vobject.__class__] -= 1 + vreg._selected[appobject.__class__] -= 1 except (KeyError, AttributeError): pass @@ -405,7 +405,7 @@ for regname, reg in testclass._env.vreg.iteritems(): if regname in skipregs: continue - for vobjects in reg.itervalues(): - for vobject in vobjects: - if not reg._selected.get(vobject): - print 'not tested', regname, vobject + for appobjects in reg.itervalues(): + for appobject in appobjects: + if not reg._selected.get(appobject): + print 'not tested', regname, appobject diff -r a93ae0f6c0ad -r de974465d381 entities/test/unittest_base.py --- a/entities/test/unittest_base.py Mon Aug 03 14:14:07 2009 +0200 +++ b/entities/test/unittest_base.py Mon Aug 03 15:16:47 2009 +0200 @@ -266,7 +266,7 @@ class MyUser(CWUser): __implements__ = (IMileStone,) self.vreg._loadedmods[__name__] = {} - self.vreg.register_vobject_class(MyUser) + self.vreg.register_appobject_class(MyUser) self.failUnless(implements(CWUser, IWorkflowable)) self.failUnless(implements(MyUser, IMileStone)) self.failUnless(implements(MyUser, IWorkflowable)) @@ -290,7 +290,7 @@ for etype in ('Company', 'Division', 'SubDivision'): class Foo(AnyEntity): id = etype - self.vreg.register_vobject_class(Foo) + self.vreg.register_appobject_class(Foo) eclass = self.select_eclass('SubDivision') if etype == 'SubDivision': self.failUnless(eclass is Foo) diff -r a93ae0f6c0ad -r de974465d381 etwist/server.py --- a/etwist/server.py Mon Aug 03 14:14:07 2009 +0200 +++ b/etwist/server.py Mon Aug 03 15:16:47 2009 +0200 @@ -330,7 +330,7 @@ def _gc_debug(): import gc from pprint import pprint - from cubicweb.vregistry import VObject + from cubicweb.appobject import AppObject gc.collect() count = 0 acount = 0 @@ -338,7 +338,7 @@ for obj in gc.get_objects(): if isinstance(obj, CubicWebTwistedRequestAdapter): count += 1 - elif isinstance(obj, VObject): + elif isinstance(obj, AppObject): acount += 1 else: try: diff -r a93ae0f6c0ad -r de974465d381 etwist/twconfig.py --- a/etwist/twconfig.py Mon Aug 03 14:14:07 2009 +0200 +++ b/etwist/twconfig.py Mon Aug 03 15:16:47 2009 +0200 @@ -87,8 +87,8 @@ options = merge_options(TwistedConfiguration.options + ServerConfiguration.options) - cubicweb_vobject_path = TwistedConfiguration.cubicweb_vobject_path | ServerConfiguration.cubicweb_vobject_path - cube_vobject_path = TwistedConfiguration.cube_vobject_path | ServerConfiguration.cube_vobject_path + cubicweb_appobject_path = TwistedConfiguration.cubicweb_appobject_path | ServerConfiguration.cubicweb_appobject_path + cube_appobject_path = TwistedConfiguration.cube_appobject_path | ServerConfiguration.cube_appobject_path def pyro_enabled(self): """tell if pyro is activated for the in memory repository""" return self['pyro-server'] diff -r a93ae0f6c0ad -r de974465d381 goa/goaconfig.py --- a/goa/goaconfig.py Mon Aug 03 14:14:07 2009 +0200 +++ b/goa/goaconfig.py Mon Aug 03 15:16:47 2009 +0200 @@ -81,9 +81,9 @@ options = [(optname, optdict) for optname, optdict in options if not optname in UNSUPPORTED_OPTIONS] - cubicweb_vobject_path = WebConfiguration.cubicweb_vobject_path | ServerConfiguration.cubicweb_vobject_path - cubicweb_vobject_path = list(cubicweb_vobject_path) + ['goa/appobjects'] - cube_vobject_path = WebConfiguration.cube_vobject_path | ServerConfiguration.cube_vobject_path + cubicweb_appobject_path = WebConfiguration.cubicweb_appobject_path | ServerConfiguration.cubicweb_appobject_path + cubicweb_appobject_path = list(cubicweb_appobject_path) + ['goa/appobjects'] + cube_appobject_path = WebConfiguration.cube_appobject_path | ServerConfiguration.cube_appobject_path # use file system schema bootstrap_schema = read_instance_schema = False diff -r a93ae0f6c0ad -r de974465d381 goa/goavreg.py --- a/goa/goavreg.py Mon Aug 03 14:14:07 2009 +0200 +++ b/goa/goavreg.py Mon Aug 03 15:16:47 2009 +0200 @@ -59,7 +59,7 @@ self.load_module(obj) def _auto_load(self, path, loadschema, cube=None): - vobjpath = self.config.cube_vobject_path + vobjpath = self.config.cube_appobject_path for filename in listdir(path): if filename[-3:] == '.py' and filename[:-3] in vobjpath: self._import(_pkg_name(cube, filename[:-3])) diff -r a93ae0f6c0ad -r de974465d381 selectors.py --- a/selectors.py Mon Aug 03 14:14:07 2009 +0200 +++ b/selectors.py Mon Aug 03 15:16:47 2009 +0200 @@ -53,8 +53,8 @@ from cubicweb import (Unauthorized, NoSelectableObject, NotAnEntity, role, typed_eid) -from cubicweb.vregistry import (NoSelectableObject, Selector, - chainall, objectify_selector) +# even if not used, let yes here so it's importable through this module +from cubicweb.appobject import Selector, objectify_selector, yes from cubicweb.cwconfig import CubicWebConfiguration from cubicweb.schema import split_expression @@ -274,17 +274,6 @@ # very basic selectors ######################################################## -class yes(Selector): - """return arbitrary score - - default score of 0.5 so any other selector take precedence - """ - def __init__(self, score=0.5): - self.score = score - - def __call__(self, *args, **kwargs): - return self.score - @objectify_selector @lltrace def none_rset(cls, req, rset=None, **kwargs): @@ -975,6 +964,7 @@ # XXX DEPRECATED ############################################################## +from cubicweb.vregistry import chainall yes_selector = deprecated()(yes) norset_selector = deprecated()(none_rset) @@ -1040,7 +1030,7 @@ accept_selector = deprecated()(accept) accept_one = deprecated()(chainall(one_line_rset, accept, - name='accept_one')) + name='accept_one')) accept_one_selector = deprecated()(accept_one) diff -r a93ae0f6c0ad -r de974465d381 server/serverconfig.py --- a/server/serverconfig.py Mon Aug 03 14:14:07 2009 +0200 +++ b/server/serverconfig.py Mon Aug 03 15:16:47 2009 +0200 @@ -85,8 +85,8 @@ SCHEMAS_LIB_DIR = '/usr/share/cubicweb/schemas/' BACKUP_DIR = '/var/lib/cubicweb/backup/' - cubicweb_vobject_path = CubicWebConfiguration.cubicweb_vobject_path | set(['sobjects']) - cube_vobject_path = CubicWebConfiguration.cube_vobject_path | set(['sobjects', 'hooks']) + cubicweb_appobject_path = CubicWebConfiguration.cubicweb_appobject_path | set(['sobjects']) + cube_appobject_path = CubicWebConfiguration.cube_appobject_path | set(['sobjects', 'hooks']) options = merge_options(( # ctl configuration diff -r a93ae0f6c0ad -r de974465d381 test/unittest_rtags.py --- a/test/unittest_rtags.py Mon Aug 03 14:14:07 2009 +0200 +++ b/test/unittest_rtags.py Mon Aug 03 15:16:47 2009 +0200 @@ -39,7 +39,7 @@ # __rtags__ = { # ('evaluee', 'Note', 'subject') : set(('inlineview',)), # } -# self.vreg.register_vobject_class(Personne2) +# self.vreg.register_appobject_class(Personne2) # rtags = Personne2.rtags # self.assertEquals(rtags.rtag('evaluee', 'Note', 'subject'), set(('inlineview', 'link'))) # self.assertEquals(rtags.is_inlined('evaluee', 'Note', 'subject'), True) diff -r a93ae0f6c0ad -r de974465d381 test/unittest_selectors.py --- a/test/unittest_selectors.py Mon Aug 03 14:14:07 2009 +0200 +++ b/test/unittest_selectors.py Mon Aug 03 15:16:47 2009 +0200 @@ -9,7 +9,7 @@ from logilab.common.testlib import TestCase, unittest_main from cubicweb.devtools.testlib import EnvBasedTC -from cubicweb.vregistry import Selector, AndSelector, OrSelector +from cubicweb.appobject import Selector, AndSelector, OrSelector from cubicweb.selectors import implements, match_user_groups from cubicweb.interfaces import IDownloadable from cubicweb.web import action @@ -91,7 +91,7 @@ class ImplementsSelectorTC(EnvBasedTC): def test_etype_priority(self): req = self.request() - cls = self.vreg.etype_class('File') + cls = self.vreg['etypes'].etype_class('File') anyscore = implements('Any').score_class(cls, req) idownscore = implements(IDownloadable).score_class(cls, req) self.failUnless(idownscore > anyscore, (idownscore, anyscore)) @@ -99,7 +99,7 @@ self.failUnless(filescore > idownscore, (filescore, idownscore)) def test_etype_inheritance_no_yams_inheritance(self): - cls = self.vreg.etype_class('Personne') + cls = self.vreg['etypes'].etype_class('Personne') self.failIf(implements('Societe').score_class(cls, self.request())) @@ -111,7 +111,7 @@ category = 'foo' __select__ = match_user_groups('owners') self.vreg._loadedmods[__name__] = {} - self.vreg.register_vobject_class(SomeAction) + self.vreg.register_appobject_class(SomeAction) self.failUnless(SomeAction in self.vreg['actions']['yo'], self.vreg['actions']) try: # login as a simple user diff -r a93ae0f6c0ad -r de974465d381 test/unittest_vregistry.py --- a/test/unittest_vregistry.py Mon Aug 03 14:14:07 2009 +0200 +++ b/test/unittest_vregistry.py Mon Aug 03 15:16:47 2009 +0200 @@ -10,7 +10,7 @@ from os.path import join from cubicweb import CW_SOFTWARE_ROOT as BASE -from cubicweb.vregistry import VObject +from cubicweb.appobject import AppObject from cubicweb.cwvreg import CubicWebVRegistry, UnknownProperty from cubicweb.devtools import TestServerConfiguration from cubicweb.interfaces import IMileStone @@ -43,7 +43,7 @@ def test___selectors__compat(self): myselector1 = lambda *args: 1 myselector2 = lambda *args: 1 - class AnAppObject(VObject): + class AnAppObject(AppObject): __selectors__ = (myselector1, myselector2) AnAppObject.build___select__() self.assertEquals(AnAppObject.__select__(AnAppObject), 2) @@ -53,7 +53,7 @@ self.failUnless(self.vreg.property_info('system.version.cubicweb')) self.assertRaises(UnknownProperty, self.vreg.property_info, 'a.non.existent.key') - def test_load_subinterface_based_vobjects(self): + def test_load_subinterface_based_appobjects(self): self.vreg.reset() self.vreg.register_objects([join(BASE, 'web', 'views', 'iprogress.py')]) # check progressbar was kicked @@ -62,7 +62,7 @@ __implements__ = (IMileStone,) self.vreg.reset() self.vreg._loadedmods[__name__] = {} - self.vreg.register_vobject_class(MyCard) + self.vreg.register_appobject_class(MyCard) self.vreg.register_objects([join(BASE, 'entities', '__init__.py'), join(BASE, 'web', 'views', 'iprogress.py')]) # check progressbar isn't kicked diff -r a93ae0f6c0ad -r de974465d381 utils.py --- a/utils.py Mon Aug 03 14:14:07 2009 +0200 +++ b/utils.py Mon Aug 03 15:16:47 2009 +0200 @@ -321,7 +321,7 @@ class AcceptMixIn(object): - """Mixin class for vobjects defining the 'accepts' attribute describing + """Mixin class for appobjects defining the 'accepts' attribute describing a set of supported entity type (Any by default). """ # XXX deprecated, no more necessary diff -r a93ae0f6c0ad -r de974465d381 vregistry.py --- a/vregistry.py Mon Aug 03 14:14:07 2009 +0200 +++ b/vregistry.py Mon Aug 03 15:16:47 2009 +0200 @@ -5,13 +5,13 @@ according to a context * to interact with the vregistry, objects should inherit from the - VObject abstract class + AppObject abstract class * the selection procedure has been generalized by delegating to a - selector, which is responsible to score the vobject according to the + selector, which is responsible to score the appobject according to the current state (req, rset, row, col). At the end of the selection, if - a vobject class has been found, an instance of this class is - returned. The selector is instantiated at vobject registration + a appobject class has been found, an instance of this class is + returned. The selector is instantiated at appobject registration :organization: Logilab @@ -22,17 +22,18 @@ __docformat__ = "restructuredtext en" import sys -import types from os import listdir, stat from os.path import dirname, join, realpath, split, isdir, exists from logging import getLogger from warnings import warn -from logilab.common.deprecation import deprecated +from logilab.common.deprecation import deprecated, class_moved +from logilab.common.logging_ext import set_log_methods -from cubicweb import CW_SOFTWARE_ROOT, set_log_methods +from cubicweb import CW_SOFTWARE_ROOT from cubicweb import (RegistryNotFound, ObjectNotFound, NoSelectableObject, RegistryOutOfDate) +from cubicweb.appobject import AppObject # XXX depending on cubicweb.web is ugly, we should deal with uicfg # reset with a good old event / callback system @@ -60,80 +61,6 @@ return _toload -class VObject(object): - """visual object, use to be handled somehow by the visual components - registry. - - The following attributes should be set on concret vobject subclasses: - - :__registry__: - name of the registry for this object (string like 'views', - 'templates'...) - :id: - 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 vobject is abstract and should not be registered - """ - # necessary attributes to interact with the registry - id = None - __registry__ = None - __select__ = None - - @classmethod - def registered(cls, registry): - """called by the registry when the vobject has been registered. - - It must return the object that will be actually registered (this - may be the right hook to create an instance for example). By - default the vobject is returned without any transformation. - """ - cls.build___select__() - return cls - - @classmethod - def selected(cls, *args, **kwargs): - """called by the registry when the vobject has been selected. - - It must return the object that will be actually returned by the - .select method (this may be the right hook to create an - instance for example). By default the selected object is - returned without any transformation. - """ - return cls - - @classmethod - def classid(cls): - """returns a unique identifier for the vobject""" - return '%s.%s' % (cls.__module__, cls.__name__) - - # XXX bw compat code - @classmethod - def build___select__(cls): - for klass in cls.mro(): - if klass.__name__ == 'AppObject': - continue # the bw compat __selector__ is there - klassdict = klass.__dict__ - if ('__select__' in klassdict and '__selectors__' in klassdict - and '__selgenerated__' not in klassdict): - raise TypeError("__select__ and __selectors__ can't be used together on class %s" % cls) - if '__selectors__' in klassdict and '__selgenerated__' not in klassdict: - cls.__selgenerated__ = True - # case where __selectors__ is defined locally (but __select__ - # is in a parent class) - selectors = klassdict['__selectors__'] - if len(selectors) == 1: - # micro optimization: don't bother with AndSelector if there's - # only one selector - select = _instantiate_selector(selectors[0]) - else: - select = AndSelector(*selectors) - cls.__select__ = select - - class Registry(dict): def __init__(self, config): @@ -155,16 +82,16 @@ oid = oid or obj.id assert oid if clear: - vobjects = self[oid] = [] + appobjects = self[oid] = [] else: - vobjects = self.setdefault(oid, []) + appobjects = self.setdefault(oid, []) # registered() is technically a classmethod but is not declared # as such because we need to compose registered in some cases - vobject = obj.registered.im_func(obj, self) - assert not vobject in vobjects, \ - 'object %s is already registered' % vobject - assert callable(vobject.__select__), vobject - vobjects.append(vobject) + appobject = obj.registered.im_func(obj, self) + assert not appobject in appobjects, \ + 'object %s is already registered' % appobject + assert callable(appobject.__select__), appobject + appobjects.append(appobject) def register_and_replace(self, obj, replaced): # XXXFIXME this is a duplication of unregister() @@ -238,13 +165,13 @@ """return an iterator on possible objects in this registry for the given context """ - for vobjects in self.itervalues(): + for appobjects in self.itervalues(): try: - yield self.select_best(vobjects, *args, **kwargs) + yield self.select_best(appobjects, *args, **kwargs) except NoSelectableObject: continue - def select_best(self, vobjects, *args, **kwargs): + def select_best(self, appobjects, *args, **kwargs): """return an instance of the most specific object according to parameters @@ -254,16 +181,16 @@ warn('only the request param can not be named when calling select', DeprecationWarning, stacklevel=3) score, winners = 0, [] - for vobject in vobjects: - vobjectscore = vobject.__select__(vobject, *args, **kwargs) - if vobjectscore > score: - score, winners = vobjectscore, [vobject] - elif vobjectscore > 0 and vobjectscore == score: - winners.append(vobject) + for appobject in appobjects: + appobjectscore = appobject.__select__(appobject, *args, **kwargs) + if appobjectscore > score: + score, winners = appobjectscore, [appobject] + elif appobjectscore > 0 and appobjectscore == score: + winners.append(appobject) if not winners: raise NoSelectableObject('args: %s\nkwargs: %s %s' % (args, kwargs.keys(), - [repr(v) for v in vobjects])) + [repr(v) for v in appobjects])) if len(winners) > 1: if self.config.mode == 'installed': self.error('select ambiguity, args: %s\nkwargs: %s %s', @@ -272,7 +199,7 @@ raise Exception('select ambiguity, args: %s\nkwargs: %s %s' % (args, kwargs.keys(), [repr(v) for v in winners])) - # return the result of the .selected method of the vobject + # return the result of the .selected method of the appobject return winners[0].selected(*args, **kwargs) @@ -379,7 +306,7 @@ vname = obj.__name__ except AttributeError: vname = obj.__class__.__name__ - self.debug('registered vobject %s in registry %s with id %s', + self.debug('registered appobject %s in registry %s with id %s', vname, registryname, oid) self._loadedmods[obj.__module__]['%s.%s' % (obj.__module__, oid)] = obj @@ -473,7 +400,7 @@ return # skip non registerable object try: - if not issubclass(obj, VObject): + if not issubclass(obj, AppObject): return except TypeError: return @@ -487,20 +414,20 @@ def load_object(self, obj): try: - self.register_vobject_class(obj) + self.register_appobject_class(obj) except Exception, ex: if self.config.mode in ('test', 'dev'): raise - self.exception('vobject %s registration failed: %s', obj, ex) + self.exception('appobject %s registration failed: %s', obj, ex) # old automatic registration XXX deprecated ############################### - def register_vobject_class(self, cls): - """handle vobject class registration + def register_appobject_class(self, cls): + """handle appobject class registration - vobject class with __abstract__ == True in their local dictionnary or + appobject class with __abstract__ == True in their local dictionnary or with a name starting starting by an underscore are not registered. - Also a vobject class needs to have __registry__ and id attributes set + Also a appobject class needs to have __registry__ and id attributes set to a non empty string to be registered. """ if (cls.__dict__.get('__abstract__') or cls.__name__[0] == '_' @@ -512,170 +439,19 @@ self.register(cls) # init logging -set_log_methods(VObject, getLogger('cubicweb.appobject')) set_log_methods(VRegistry, getLogger('cubicweb.vreg')) set_log_methods(Registry, getLogger('cubicweb.registry')) -# selector base classes and operations ######################################## - -class Selector(object): - """base class for selector classes providing implementation - 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 - - - a selector is called to help choosing the correct object for a - particular context by returning a score (`int`) telling how well - the class given as first argument apply to the given context. - - 0 score means that the class doesn't apply. - """ - - @property - def func_name(self): - # backward compatibility - return self.__class__.__name__ - - def search_selector(self, selector): - """search for the given selector or selector instance in the selectors - tree. Return it of None if not found - """ - if self is selector: - return self - if isinstance(selector, type) and isinstance(self, selector): - return self - return None - - def __str__(self): - return self.__class__.__name__ - - def __and__(self, other): - return AndSelector(self, other) - def __rand__(self, other): - return AndSelector(other, self) - - def __or__(self, other): - return OrSelector(self, other) - def __ror__(self, other): - return OrSelector(other, self) - - def __invert__(self): - return NotSelector(self) - - # XXX (function | function) or (function & function) not managed yet - - def __call__(self, cls, *args, **kwargs): - return NotImplementedError("selector %s must implement its logic " - "in its __call__ method" % self.__class__) - -class MultiSelector(Selector): - """base class for compound selector classes""" - - def __init__(self, *selectors): - self.selectors = self.merge_selectors(selectors) - - def __str__(self): - return '%s(%s)' % (self.__class__.__name__, - ','.join(str(s) for s in self.selectors)) - - @classmethod - def merge_selectors(cls, selectors): - """deal with selector instanciation when necessary and merge - multi-selectors if possible: - - AndSelector(AndSelector(sel1, sel2), AndSelector(sel3, sel4)) - ==> AndSelector(sel1, sel2, sel3, sel4) - """ - merged_selectors = [] - for selector in selectors: - try: - selector = _instantiate_selector(selector) - except: - pass - #assert isinstance(selector, Selector), selector - if isinstance(selector, cls): - merged_selectors += selector.selectors - else: - merged_selectors.append(selector) - return merged_selectors - - def search_selector(self, selector): - """search for the given selector or selector instance in the selectors - tree. Return it of None if not found - """ - for childselector in self.selectors: - if childselector is selector: - return childselector - found = childselector.search_selector(selector) - if found is not None: - return found - return None - - -def objectify_selector(selector_func): - """convenience decorator for simple selectors where a class definition - would be overkill:: - - @objectify_selector - def yes(cls, *args, **kwargs): - return 1 - - """ - return type(selector_func.__name__, (Selector,), - {'__call__': lambda self, *args, **kwargs: selector_func(*args, **kwargs)}) - -def _instantiate_selector(selector): - """ensures `selector` is a `Selector` instance - - NOTE: This should only be used locally in build___select__() - XXX: then, why not do it ?? - """ - if isinstance(selector, types.FunctionType): - return objectify_selector(selector)() - if isinstance(selector, type) and issubclass(selector, Selector): - return selector() - return selector - - -class AndSelector(MultiSelector): - """and-chained selectors (formerly known as chainall)""" - def __call__(self, cls, *args, **kwargs): - score = 0 - for selector in self.selectors: - partscore = selector(cls, *args, **kwargs) - if not partscore: - return 0 - score += partscore - return score - - -class OrSelector(MultiSelector): - """or-chained selectors (formerly known as chainfirst)""" - def __call__(self, cls, *args, **kwargs): - for selector in self.selectors: - partscore = selector(cls, *args, **kwargs) - if partscore: - return partscore - return 0 - -class NotSelector(Selector): - """negation selector""" - def __init__(self, selector): - self.selector = selector - - def __call__(self, cls, *args, **kwargs): - score = self.selector(cls, *args, **kwargs) - return int(not score) - - def __str__(self): - return 'NOT(%s)' % super(NotSelector, self).__str__() - - # XXX bw compat functions ##################################################### +from cubicweb.appobject import objectify_selector, AndSelector, OrSelector, Selector + +objectify_selector = deprecated('objectify_selector has been moved to appobject module')(objectify_selector) + +Selector = class_moved(Selector) + +@deprecated('use & operator (binary and)') def chainall(*selectors, **kwargs): """return a selector chaining given selectors. If one of the selectors fail, selection will fail, else the returned score @@ -688,6 +464,7 @@ selector.__name__ = kwargs['name'] return selector +@deprecated('use | operator (binary or)') def chainfirst(*selectors, **kwargs): """return a selector chaining given selectors. If all the selectors fail, selection will fail, else the returned score diff -r a93ae0f6c0ad -r de974465d381 web/test/test_views.py --- a/web/test/test_views.py Mon Aug 03 14:14:07 2009 +0200 +++ b/web/test/test_views.py Mon Aug 03 15:16:47 2009 +0200 @@ -52,7 +52,7 @@ def test_js_added_only_once(self): self.vreg._loadedmods[__name__] = {} - self.vreg.register_vobject_class(SomeView) + self.vreg.register_appobject_class(SomeView) rset = self.execute('CWUser X') source = self.view('someview', rset).source self.assertEquals(source.count('spam.js'), 1) diff -r a93ae0f6c0ad -r de974465d381 web/test/unittest_viewselector.py --- a/web/test/unittest_viewselector.py Mon Aug 03 14:14:07 2009 +0200 +++ b/web/test/unittest_viewselector.py Mon Aug 03 15:16:47 2009 +0200 @@ -274,7 +274,7 @@ class CWUserCreationForm(editforms.CreationFormView): __select__ = specified_etype_implements('CWUser') self.vreg._loadedmods[__name__] = {} - self.vreg.register_vobject_class(CWUserCreationForm) + self.vreg.register_appobject_class(CWUserCreationForm) req.form['etype'] = 'CWUser' self.assertIsInstance(self.vreg['views'].select('creation', req, rset=rset), CWUserCreationForm) @@ -431,7 +431,7 @@ def setUp(self): super(RQLActionTC, self).setUp() self.vreg._loadedmods[__name__] = {} - self.vreg.register_vobject_class(CWETypeRQLAction) + self.vreg.register_appobject_class(CWETypeRQLAction) def tearDown(self): super(RQLActionTC, self).tearDown() diff -r a93ae0f6c0ad -r de974465d381 web/views/actions.py --- a/web/views/actions.py Mon Aug 03 14:14:07 2009 +0200 +++ b/web/views/actions.py Mon Aug 03 15:16:47 2009 +0200 @@ -8,7 +8,7 @@ __docformat__ = "restructuredtext en" _ = unicode -from cubicweb.vregistry import objectify_selector +from cubicweb.appobject import objectify_selector from cubicweb.selectors import (EntitySelector, one_line_rset, two_lines_rset, one_etype_rset, relation_possible, nonempty_rset, non_final_entity, diff -r a93ae0f6c0ad -r de974465d381 web/views/basetemplates.py --- a/web/views/basetemplates.py Mon Aug 03 14:14:07 2009 +0200 +++ b/web/views/basetemplates.py Mon Aug 03 15:16:47 2009 +0200 @@ -10,7 +10,7 @@ from logilab.mtconverter import xml_escape -from cubicweb.vregistry import objectify_selector +from cubicweb.appobject import objectify_selector from cubicweb.selectors import match_kwargs from cubicweb.view import View, MainTemplate, NOINDEX, NOFOLLOW from cubicweb.utils import make_uid, UStringIO diff -r a93ae0f6c0ad -r de974465d381 web/views/facets.py --- a/web/views/facets.py Mon Aug 03 14:14:07 2009 +0200 +++ b/web/views/facets.py Mon Aug 03 15:16:47 2009 +0200 @@ -11,7 +11,7 @@ from logilab.mtconverter import xml_escape -from cubicweb.vregistry import objectify_selector +from cubicweb.appobject import objectify_selector from cubicweb.selectors import (non_final_entity, two_lines_rset, match_context_prop, yes, relation_possible) from cubicweb.web.box import BoxTemplate diff -r a93ae0f6c0ad -r de974465d381 web/views/forms.py --- a/web/views/forms.py Mon Aug 03 14:14:07 2009 +0200 +++ b/web/views/forms.py Mon Aug 03 15:16:47 2009 +0200 @@ -288,7 +288,8 @@ class EntityFieldsForm(FieldsForm): id = 'base' - __select__ = (match_kwargs('entity') | (one_line_rset & non_final_entity())) + __select__ = (match_kwargs('entity') + | (one_line_rset() & non_final_entity())) internal_fields = FieldsForm.internal_fields + ('__type', 'eid', '__maineid') domid = 'entityForm' diff -r a93ae0f6c0ad -r de974465d381 web/views/plots.py --- a/web/views/plots.py Mon Aug 03 14:14:07 2009 +0200 +++ b/web/views/plots.py Mon Aug 03 15:16:47 2009 +0200 @@ -16,7 +16,7 @@ from logilab.mtconverter import xml_escape from cubicweb.utils import make_uid, UStringIO, datetime2ticks -from cubicweb.vregistry import objectify_selector +from cubicweb.appobject import objectify_selector from cubicweb.web.views import baseviews @objectify_selector diff -r a93ae0f6c0ad -r de974465d381 web/views/urlpublishing.py --- a/web/views/urlpublishing.py Mon Aug 03 14:14:07 2009 +0200 +++ b/web/views/urlpublishing.py Mon Aug 03 15:16:47 2009 +0200 @@ -194,9 +194,9 @@ def evaluate_path(self, req, parts): # uri <=> req._twreq.path or req._twreq.uri uri = req.url_unquote('/' + '/'.join(parts)) - vobjects = sorted(self.vreg['urlrewriting'].all_objects(), - key=lambda x: x.priority, reverse=True) - for rewritercls in vobjects: + evaluators = sorted(self.vreg['urlrewriting'].all_objects(), + key=lambda x: x.priority, reverse=True) + for rewritercls in evaluators: rewriter = rewritercls() try: # XXX we might want to chain url rewrites diff -r a93ae0f6c0ad -r de974465d381 web/webconfig.py --- a/web/webconfig.py Mon Aug 03 14:14:07 2009 +0200 +++ b/web/webconfig.py Mon Aug 03 15:16:47 2009 +0200 @@ -63,8 +63,8 @@ """the WebConfiguration is a singleton object handling instance's configuration and preferences """ - cubicweb_vobject_path = CubicWebConfiguration.cubicweb_vobject_path | set(['web/views']) - cube_vobject_path = CubicWebConfiguration.cube_vobject_path | set(['views']) + cubicweb_appobject_path = CubicWebConfiguration.cubicweb_appobject_path | set(['web/views']) + cube_appobject_path = CubicWebConfiguration.cube_appobject_path | set(['views']) options = merge_options(CubicWebConfiguration.options + ( ('anonymous-user',