From 832c6b5aa425e1a3191affcb7185342ca21518cb Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Thu, 19 Dec 2019 05:25:57 +0100 Subject: [mod] move predicates code up in the file for the next patch This is needed for type declarations because we need to reference the Predicate class. --- logilab/common/registry.py | 417 ++++++++++++++++++++++----------------------- 1 file changed, 208 insertions(+), 209 deletions(-) diff --git a/logilab/common/registry.py b/logilab/common/registry.py index 3310864..86d5219 100644 --- a/logilab/common/registry.py +++ b/logilab/common/registry.py @@ -97,6 +97,214 @@ from logilab.common.decorators import classproperty from logilab.common.deprecation import deprecated +# selector base classes and operations ######################################## + +def objectify_predicate(selector_func): + """Most of the time, a simple score function is enough to build a selector. + The :func:`objectify_predicate` decorator turn it into a proper selector + class:: + + @objectify_predicate + def one(cls, req, rset=None, **kwargs): + return 1 + + class MyView(View): + __select__ = View.__select__ & one() + + """ + return type(selector_func.__name__, (Predicate,), + {'__doc__': selector_func.__doc__, + '__call__': lambda self, *a, **kw: selector_func(*a, **kw)}) + + +_PREDICATES: Dict[int, Type] = {} + +def wrap_predicates(decorator): + for predicate in _PREDICATES.values(): + if not '_decorators' in predicate.__dict__: + predicate._decorators = set() + if decorator in predicate._decorators: + continue + predicate._decorators.add(decorator) + predicate.__call__ = decorator(predicate.__call__) + +class PredicateMetaClass(type): + def __new__(mcs, *args, **kwargs): + # use __new__ so subclasses doesn't have to call Predicate.__init__ + inst = type.__new__(mcs, *args, **kwargs) + proxy = weakref.proxy(inst, lambda p: _PREDICATES.pop(id(p))) + _PREDICATES[id(proxy)] = proxy + return inst + + +class Predicate(object, metaclass=PredicateMetaClass): + """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 :meth:`__call__` method. Notice it + should usually accept any arbitrary arguments (the context), though that may + vary depending on your usage of the registry. + + a selector is called to help choosing the correct object for a + particular context by returning a score (`int`) telling how well + the implementation given as first argument fit 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, selector instance or tuple of + selectors in the selectors tree. Return None if not found. + """ + if self is selector: + return self + if (isinstance(selector, type) or isinstance(selector, tuple)) and \ + isinstance(self, selector): + return self + return None + + def __str__(self): + return self.__class__.__name__ + + def __and__(self, other): + return AndPredicate(self, other) + def __rand__(self, other): + return AndPredicate(other, self) + def __iand__(self, other): + return AndPredicate(self, other) + def __or__(self, other): + return OrPredicate(self, other) + def __ror__(self, other): + return OrPredicate(other, self) + def __ior__(self, other): + return OrPredicate(self, other) + + def __invert__(self): + return NotPredicate(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__) + + def __repr__(self): + return u'' % (self.__class__.__name__, id(self)) + + +class MultiPredicate(Predicate): + """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: + + AndPredicate(AndPredicate(sel1, sel2), AndPredicate(sel3, sel4)) + ==> AndPredicate(sel1, sel2, sel3, sel4) + """ + merged_selectors = [] + for selector in selectors: + # XXX do we really want magic-transformations below? + # if so, wanna warn about them? + if isinstance(selector, types.FunctionType): + selector = objectify_predicate(selector)() + if isinstance(selector, type) and issubclass(selector, Predicate): + selector = selector() + assert isinstance(selector, Predicate), 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 (or tuple of + selectors) in the selectors tree. Return 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 + # if not found in children, maybe we are looking for self? + return super(MultiPredicate, self).search_selector(selector) + + +class AndPredicate(MultiPredicate): + """and-chained selectors""" + 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 OrPredicate(MultiPredicate): + """or-chained selectors""" + def __call__(self, cls, *args, **kwargs): + for selector in self.selectors: + partscore = selector(cls, *args, **kwargs) + if partscore: + return partscore + return 0 + +class NotPredicate(Predicate): + """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)' % self.selector + + +class yes(Predicate): # pylint: disable=C0103 + """Return the score given as parameter, with a default score of 0.5 so any + other selector take precedence. + + Usually used for objects 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'... + """ + def __init__(self, score=0.5): + self.score = score + + def __call__(self, *args, **kwargs): + return self.score + + +# deprecated stuff ############################################################# + +@deprecated('[lgc 0.59] use Registry.objid class method instead') +def classid(cls): + return '%s.%s' % (cls.__module__, cls.__name__) + +@deprecated('[lgc 0.59] use obj_registries function instead') +def class_registries(cls, registryname): + return obj_registries(cls, registryname) + class RegistryException(Exception): """Base class for registry exception.""" @@ -990,212 +1198,3 @@ class traced_selection(object): # pylint: disable=C0103 global TRACED_OIDS TRACED_OIDS = None return traceback is None - -# selector base classes and operations ######################################## - -def objectify_predicate(selector_func): - """Most of the time, a simple score function is enough to build a selector. - The :func:`objectify_predicate` decorator turn it into a proper selector - class:: - - @objectify_predicate - def one(cls, req, rset=None, **kwargs): - return 1 - - class MyView(View): - __select__ = View.__select__ & one() - - """ - return type(selector_func.__name__, (Predicate,), - {'__doc__': selector_func.__doc__, - '__call__': lambda self, *a, **kw: selector_func(*a, **kw)}) - - -_PREDICATES: Dict[int, Type] = {} - -def wrap_predicates(decorator): - for predicate in _PREDICATES.values(): - if not '_decorators' in predicate.__dict__: - predicate._decorators = set() - if decorator in predicate._decorators: - continue - predicate._decorators.add(decorator) - predicate.__call__ = decorator(predicate.__call__) - -class PredicateMetaClass(type): - def __new__(mcs, *args, **kwargs): - # use __new__ so subclasses doesn't have to call Predicate.__init__ - inst = type.__new__(mcs, *args, **kwargs) - proxy = weakref.proxy(inst, lambda p: _PREDICATES.pop(id(p))) - _PREDICATES[id(proxy)] = proxy - return inst - - -class Predicate(object, metaclass=PredicateMetaClass): - """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 :meth:`__call__` method. Notice it - should usually accept any arbitrary arguments (the context), though that may - vary depending on your usage of the registry. - - a selector is called to help choosing the correct object for a - particular context by returning a score (`int`) telling how well - the implementation given as first argument fit 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, selector instance or tuple of - selectors in the selectors tree. Return None if not found. - """ - if self is selector: - return self - if (isinstance(selector, type) or isinstance(selector, tuple)) and \ - isinstance(self, selector): - return self - return None - - def __str__(self): - return self.__class__.__name__ - - def __and__(self, other): - return AndPredicate(self, other) - def __rand__(self, other): - return AndPredicate(other, self) - def __iand__(self, other): - return AndPredicate(self, other) - def __or__(self, other): - return OrPredicate(self, other) - def __ror__(self, other): - return OrPredicate(other, self) - def __ior__(self, other): - return OrPredicate(self, other) - - def __invert__(self): - return NotPredicate(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__) - - def __repr__(self): - return u'' % (self.__class__.__name__, id(self)) - - -class MultiPredicate(Predicate): - """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: - - AndPredicate(AndPredicate(sel1, sel2), AndPredicate(sel3, sel4)) - ==> AndPredicate(sel1, sel2, sel3, sel4) - """ - merged_selectors = [] - for selector in selectors: - # XXX do we really want magic-transformations below? - # if so, wanna warn about them? - if isinstance(selector, types.FunctionType): - selector = objectify_predicate(selector)() - if isinstance(selector, type) and issubclass(selector, Predicate): - selector = selector() - assert isinstance(selector, Predicate), 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 (or tuple of - selectors) in the selectors tree. Return 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 - # if not found in children, maybe we are looking for self? - return super(MultiPredicate, self).search_selector(selector) - - -class AndPredicate(MultiPredicate): - """and-chained selectors""" - 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 OrPredicate(MultiPredicate): - """or-chained selectors""" - def __call__(self, cls, *args, **kwargs): - for selector in self.selectors: - partscore = selector(cls, *args, **kwargs) - if partscore: - return partscore - return 0 - -class NotPredicate(Predicate): - """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)' % self.selector - - -class yes(Predicate): # pylint: disable=C0103 - """Return the score given as parameter, with a default score of 0.5 so any - other selector take precedence. - - Usually used for objects 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'... - """ - def __init__(self, score=0.5): - self.score = score - - def __call__(self, *args, **kwargs): - return self.score - - -# deprecated stuff ############################################################# - -@deprecated('[lgc 0.59] use Registry.objid class method instead') -def classid(cls): - return '%s.%s' % (cls.__module__, cls.__name__) - -@deprecated('[lgc 0.59] use obj_registries function instead') -def class_registries(cls, registryname): - return obj_registries(cls, registryname) - -- cgit v1.2.1