diff -r 26e242ba3da5 -r d13830a2adfd appobject.py --- a/appobject.py Fri May 21 13:35:08 2010 +0200 +++ b/appobject.py Fri May 21 15:00:01 2010 +0200 @@ -39,6 +39,92 @@ from logilab.common.decorators import classproperty from logilab.common.logging_ext import set_log_methods +from cubicweb.cwconfig import CubicWebConfiguration + +def class_regid(cls): + """returns a unique identifier for an appobject class""" + if 'id' in cls.__dict__: + warn('[3.6] %s.%s: id is deprecated, use __regid__' + % (cls.__module__, cls.__name__), DeprecationWarning) + cls.__regid__ = cls.id + if hasattr(cls, 'id') and not isinstance(cls.id, property): + return cls.id + return cls.__regid__ + +# helpers for debugging selectors +TRACED_OIDS = None + +def _trace_selector(cls, selector, args, ret): + # /!\ lltrace decorates pure function or __call__ method, this + # means argument order may be different + if isinstance(cls, Selector): + selname = str(cls) + vobj = args[0] + else: + selname = selector.__name__ + vobj = cls + if TRACED_OIDS == 'all' or class_regid(vobj) in TRACED_OIDS: + #SELECTOR_LOGGER.warning('selector %s returned %s for %s', selname, ret, cls) + print '%s -> %s for %s(%s)' % (selname, ret, vobj, vobj.__regid__) + +def lltrace(selector): + """use this decorator on your selectors so the becomes traceable with + :class:`traced_selection` + """ + # don't wrap selectors if not in development mode + if CubicWebConfiguration.mode == 'system': # XXX config.debug + return selector + def traced(cls, *args, **kwargs): + ret = selector(cls, *args, **kwargs) + if TRACED_OIDS is not None: + _trace_selector(cls, selector, args, ret) + return ret + traced.__name__ = selector.__name__ + traced.__doc__ = selector.__doc__ + return traced + +class traced_selection(object): + """ + Typical usage is : + + .. 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. + + This will yield lines like this in the logs:: + + selector one_line_rset returned 0 for + + 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( ('regid1', 'regid2') ): + ... # some code in which you want to debug selectors + ... # for objects with __regid__ 'regid1' and 'regid2' + + A potentially usefull point to set up such a tracing function is + the `cubicweb.vregistry.Registry.select` method body. + """ + + def __init__(self, traced='all'): + self.traced = traced + + def __enter__(self): + global TRACED_OIDS + TRACED_OIDS = self.traced + + def __exit__(self, exctype, exc, traceback): + global TRACED_OIDS + TRACED_OIDS = None + return traceback is None # selector base classes and operations ######################################## @@ -175,6 +261,7 @@ class AndSelector(MultiSelector): """and-chained selectors (formerly known as chainall)""" + @lltrace def __call__(self, cls, *args, **kwargs): score = 0 for selector in self.selectors: @@ -187,6 +274,7 @@ class OrSelector(MultiSelector): """or-chained selectors (formerly known as chainfirst)""" + @lltrace def __call__(self, cls, *args, **kwargs): for selector in self.selectors: partscore = selector(cls, *args, **kwargs) @@ -199,6 +287,7 @@ def __init__(self, selector): self.selector = selector + @lltrace def __call__(self, cls, *args, **kwargs): score = self.selector(cls, *args, **kwargs) return int(not score)