1 """ A universal module with functions / classes without dependencies. """ 2 import functools 3 import re 4 import os 5 6 7 _sep = os.path.sep 8 if os.path.altsep is not None: 9 _sep += os.path.altsep 10 _path_re = re.compile(r'(?:\.[^{0}]+|[{0}]__init__\.py)$'.format(re.escape(_sep))) 11 del _sep 12 13 14 def to_list(func): 15 def wrapper(*args, **kwargs): 16 return list(func(*args, **kwargs)) 17 return wrapper 18 19 20 def to_tuple(func): 21 def wrapper(*args, **kwargs): 22 return tuple(func(*args, **kwargs)) 23 return wrapper 24 25 26 def unite(iterable): 27 """Turns a two dimensional array into a one dimensional.""" 28 return set(typ for types in iterable for typ in types) 29 30 31 class UncaughtAttributeError(Exception): 32 """ 33 Important, because `__getattr__` and `hasattr` catch AttributeErrors 34 implicitly. This is really evil (mainly because of `__getattr__`). 35 Therefore this class originally had to be derived from `BaseException` 36 instead of `Exception`. But because I removed relevant `hasattr` from 37 the code base, we can now switch back to `Exception`. 38 39 :param base: return values of sys.exc_info(). 40 """ 41 42 43 def safe_property(func): 44 return property(reraise_uncaught(func)) 45 46 47 def reraise_uncaught(func): 48 """ 49 Re-throw uncaught `AttributeError`. 50 51 Usage: Put ``@rethrow_uncaught`` in front of the function 52 which does **not** suppose to raise `AttributeError`. 53 54 AttributeError is easily get caught by `hasattr` and another 55 ``except AttributeError`` clause. This becomes problem when you use 56 a lot of "dynamic" attributes (e.g., using ``@property``) because you 57 can't distinguish if the property does not exist for real or some code 58 inside of the "dynamic" attribute through that error. In a well 59 written code, such error should not exist but getting there is very 60 difficult. This decorator is to help us getting there by changing 61 `AttributeError` to `UncaughtAttributeError` to avoid unexpected catch. 62 This helps us noticing bugs earlier and facilitates debugging. 63 """ 64 @functools.wraps(func) 65 def wrapper(*args, **kwds): 66 try: 67 return func(*args, **kwds) 68 except AttributeError as e: 69 raise UncaughtAttributeError(e) from e 70 return wrapper 71 72 73 class PushBackIterator: 74 def __init__(self, iterator): 75 self.pushes = [] 76 self.iterator = iterator 77 self.current = None 78 79 def push_back(self, value): 80 self.pushes.append(value) 81 82 def __iter__(self): 83 return self 84 85 def __next__(self): 86 if self.pushes: 87 self.current = self.pushes.pop() 88 else: 89 self.current = next(self.iterator) 90 return self.current 91