""" A universal module with functions / classes without dependencies. """ import functools import re import os _sep = os.path.sep if os.path.altsep is not None: _sep += os.path.altsep _path_re = re.compile(r'(?:\.[^{0}]+|[{0}]__init__\.py)$'.format(re.escape(_sep))) del _sep def to_list(func): def wrapper(*args, **kwargs): return list(func(*args, **kwargs)) return wrapper def to_tuple(func): def wrapper(*args, **kwargs): return tuple(func(*args, **kwargs)) return wrapper def unite(iterable): """Turns a two dimensional array into a one dimensional.""" return set(typ for types in iterable for typ in types) class UncaughtAttributeError(Exception): """ Important, because `__getattr__` and `hasattr` catch AttributeErrors implicitly. This is really evil (mainly because of `__getattr__`). Therefore this class originally had to be derived from `BaseException` instead of `Exception`. But because I removed relevant `hasattr` from the code base, we can now switch back to `Exception`. :param base: return values of sys.exc_info(). """ def safe_property(func): return property(reraise_uncaught(func)) def reraise_uncaught(func): """ Re-throw uncaught `AttributeError`. Usage: Put ``@rethrow_uncaught`` in front of the function which does **not** suppose to raise `AttributeError`. AttributeError is easily get caught by `hasattr` and another ``except AttributeError`` clause. This becomes problem when you use a lot of "dynamic" attributes (e.g., using ``@property``) because you can't distinguish if the property does not exist for real or some code inside of the "dynamic" attribute through that error. In a well written code, such error should not exist but getting there is very difficult. This decorator is to help us getting there by changing `AttributeError` to `UncaughtAttributeError` to avoid unexpected catch. This helps us noticing bugs earlier and facilitates debugging. """ @functools.wraps(func) def wrapper(*args, **kwds): try: return func(*args, **kwds) except AttributeError as e: raise UncaughtAttributeError(e) from e return wrapper class PushBackIterator: def __init__(self, iterator): self.pushes = [] self.iterator = iterator self.current = None def push_back(self, value): self.pushes.append(value) def __iter__(self): return self def __next__(self): if self.pushes: self.current = self.pushes.pop() else: self.current = next(self.iterator) return self.current