utils.py 2.6 KB

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