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