Source code for order.util

# coding: utf-8

"""
Helpful utilities.
"""


__all__ = [
    "ROOT_DEFAULT", "typed", "make_list", "multi_match", "flatten", "to_root_latex",
    "join_root_selection", "join_numexpr_selection", "class_id", "args_to_kwargs", "DotAccessProxy",
]


import os
import functools
import types
import re
import fnmatch
import inspect

import six


#: Boolean value that denotes if ROOT-style selection strings, latex labels, etc, are used by
#: default. The value defaults to *False* and can be altered by setting
#:
#: .. code-block:: python
#:
#:     os.environ["ORDER_ROOT_DEFAULT"] = 1|"1"|"yes"|"true"
#:
#: before importing order. This rather peculiar mechanism is favored over e.g. using a setter since
#: also default values of various methods across the order package depend on this flag.
ROOT_DEFAULT = False
if str(os.environ.get("ORDER_ROOT_DEFAULT", "0")).lower() in ("1", "yes", "true"):
    ROOT_DEFAULT = True


[docs]class typed(property): """ Shorthand for the most common property definition. Can be used as a decorator to wrap around a single function. Example: .. code-block:: python class MyClass(object): def __init__(self): self._foo = None @typed def foo(self, foo): if not isinstance(foo, str): raise TypeError("not a string: '%s'" % foo) return foo myInstance = MyClass() myInstance.foo = 123 -> TypeError myInstance.foo = "bar" -> ok print(myInstance.foo) -> prints "bar" In the exampe above, set/get calls target the instance member ``_foo``, i.e. "_<function_name>". The member name can be configured by setting *name*. If *setter* (*deleter*) is *True* (the default), a setter (deleter) method is booked as well. Prior to updating the member when the setter is called, *fparse* is invoked which may implement sanity checks. """ def __init__(self, fparse=None, setter=True, deleter=True, name=None): self._args = (setter, deleter, name) # only register the property if fparse is set if fparse is not None: self.fparse = fparse # build the default name if name is None: name = fparse.__name__ self.__name__ = name # the name of the wrapped member m_name = "_" + name # call the super constructor with generated methods property.__init__(self, functools.wraps(fparse)(self._fget(m_name)), self._fset(m_name) if setter else None, self._fdel(m_name) if deleter else None, ) def __call__(self, fparse): return self.__class__(fparse, *self._args) def _fget(self, name): """ Build and returns the property's *fget* method for the member defined by *name*. """ def fget(inst): return getattr(inst, name) return fget def _fset(self, name): """ Build and returns the property's *fdel* method for the member defined by *name*. """ def fset(inst, value): # the setter uses the wrapped function as well # to allow for value checks value = self.fparse(inst, value) setattr(inst, name, value) return fset def _fdel(self, name): """ Build and returns the property's *fdel* method for the member defined by *name*. """ def fdel(inst): delattr(inst, name) return fdel
[docs]def make_list(obj, cast=True): """ Converts an object *obj* to a list and returns it. Objects of types *tuple* and *set* are converted if *cast* is *True*. Otherwise, and for all other types, *obj* is put in a new list. """ if isinstance(obj, list): return list(obj) if isinstance(obj, types.GeneratorType): return list(obj) if isinstance(obj, (tuple, set)) and cast: return list(obj) return [obj]
[docs]def multi_match(name, patterns, mode=any, func="fnmatch", *args, **kwargs): """ Compares *name* to multiple *patterns* and returns *True* in case of at least one match (*mode* = *any*, the default), or in case all patterns matched (*mode* = *all*). Otherwise, *False* is returned. *func* determines the matching function and accepts ``"fnmatch"``, ``"fnmatchcase"``, and ``"re"``. All *args* and *kwargs* are passed to the actual matching function. """ if func == "re": return mode(re.match(pattern, name, *args, **kwargs) for pattern in patterns) if func in ("fnmatch", "fnmatchcase"): _func = getattr(fnmatch, func) return mode(_func(name, pattern, *args, **kwargs) for pattern in patterns) raise ValueError("unknown matching function: {}".format(func))
[docs]def flatten(struct, depth=-1): """ Flattens and returns a complex structured object *struct* up to a certain *depth*. When *depth* is negative, *struct* is flattened entirely. """ # generator are not considered as a flattening step, so pass the same depth if isinstance(struct, types.GeneratorType): return flatten(list(struct), depth=depth) # stopping criterion if depth == 0: return struct # actual flattening if isinstance(struct, dict): return flatten(struct.values(), depth=depth - 1) if isinstance(struct, (list, tuple, set)): objs = [] for obj in struct: objs.extend(flatten(obj, depth=depth - 1)) return objs return [struct]
def try_float(obj): """ Tries to cast *obj* to float and returns it. If the conversion fails, *None* is returned. """ try: return float(obj) except: return None
[docs]def to_root_latex(s): """ Converts latex expressions in a string *s* to ROOT-compatible latex. """ return s.replace("$", "").replace("\\", "#")
def _parse_selection(*selection): """ Parser for *selection* strings used in :py:func:`join_root_selection` and :py:func:`join_numexpr_selection`. """ _selection = [] for s in flatten(selection): if isinstance(s, (int, float)): # special case: skip ones if s == 1: continue elif isinstance(s, six.string_types): # special case: skip empty strings and ones if not s.strip() or try_float(s) == 1: continue else: raise Exception("invalid selection string: {}".format(s)) _selection.append(str(s)) return _selection def _bracket(s, force=False): embed = force or not s.startswith("(") or not s.endswith(")") if not embed: # outer brace characters might not be the enclosing brace, e.g. "(foo) && (bar)" balance = 0 diffs = {"(": 1, ")": -1} for i, c in enumerate(s): balance += diffs.get(c, 0) if balance == 0 and i < len(s) - 1: embed = True break return "({})".format(s) if embed else s def _join_selection(selection, op, bracket): selection = _parse_selection(selection) # trivial case, no selection if not selection: return "1" # prepare the concatenation op op = " {} ".format(op.strip()) # build the joined selection string if len(selection) == 1: joined = selection[0] else: joined = op.join(_bracket(s) for s in selection) # check if brackets should be placed around the joined selection string return _bracket(joined, force=True) if bracket else joined _root_ops = {"and": "&&", "or": "||", "mul": "*", "div": "/", "plus": "+", "minus": "-"} _numexpr_ops = {"and": "&", "or": "|", "mul": "*", "div": "/", "plus": "+", "minus": "-"}
[docs]def join_root_selection(*selection, **kwargs): """ join_root_selection(*selection, op="&&", bracket=False) Returns a concatenation of root *selection* strings, which is done by default via logical *AND*. (*op*). When *bracket* is *True*, the final selection string is placed into brackets. """ op = kwargs.get("op", "&&") op = _root_ops.get(op.lower(), op) bracket = kwargs.get("bracket", False) return _join_selection(selection, op, bracket)
[docs]def join_numexpr_selection(*selection, **kwargs): """ join_numexpr_selection(*selection, op="&", bracket=False) Returns a concatenation of numexpr *selection* strings, which is done by default via logical *AND*. (*op*). When *bracket* is *True*, the final selection string is placed into brackets. """ op = kwargs.get("op", "&") op = _numexpr_ops.get(op.lower(), op) bracket = kwargs.get("bracket", False) return _join_selection(selection, op, bracket)
[docs]def class_id(cls): """ Returns the full id of a *class*, i.e., the id of the module it is defined in, extended by the name of the class. Example: .. code-block:: python # module a.b class MyClass(object): ... class_id(MyClass) # "a.b.MyClass" """ name = cls.__name__ module = cls.__module__ # skip empty and builtin modules return name if not module or module == str.__module__ else (module + "." + name)
[docs]def args_to_kwargs(func, args): """ Converts arguments *args* passed to a function *func* to a dictionary that can be used as keyword arguments. Internally, ``inspect.getargspec`` is used to get the names of arguments in the function signature. Example: .. code-block:: python def func(a, b, c=1): ... def wrapper(*args, **kwargs): kwargs.update(args_to_kwargs(func, args)) # kwargs now contains the initial args and kwargs for easy parsing ... return func(**kwargs) """ if six.PY2: ismethod = inspect.ismethod(func) arg_names = inspect.getargspec(func).args[int(ismethod):] else: arg_names = list(inspect.signature(func).parameters.keys()) return dict(zip(arg_names[:len(args)], args))
[docs]class DotAccessProxy(object): """ Proxy object that provides simple attribute access to values that are retrieved by a *getter* and optionally set through a *setter*. Example: .. code-block:: python my_dict = {"foo": 123} proxy = DotAccessProxy(my_dict.__getattr__) proxy.foo # -> 123 proxy.bar # -> AttributeError proxy = DotAccessProxy(my_dict.get) proxy.foo # -> 123 proxy.bar # -> None proxy = DotAccessProxy(my_dict.get, my_dict.__setitem__) proxy.foo # -> 123 proxy.bar # -> None proxy.bar = 99 proxy.bar # -> 99 """ def __init__(self, getter, setter=None): super(DotAccessProxy, self).__init__() self._getter = getter self._setter = setter def __call__(self, *args, **kwargs): return self._getter(*args, **kwargs) def __getattr__(self, attr): if attr.startswith("__") or attr in ("_getter", "_setter"): return super(DotAccessProxy, self).__getattr__(attr) try: return self._getter(attr) except KeyError as e: raise AttributeError(*e.args) def __setattr__(self, attr, value): if attr.startswith("__") or attr in ("_getter", "_setter"): super(DotAccessProxy, self).__setattr__(attr, value) else: setter = self._setter if setter is None: cls_name = self.__class__.__name__ raise Exception("cannot set attribute, setter not defined on {}".format(cls_name)) setter(attr, value)