util.py 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. """Utility functions.
  2. """
  3. import contextlib
  4. import multiprocessing
  5. import sys
  6. from pathlib import Path
  7. from milc import cli
  8. maybe_exit_should_exit = True
  9. maybe_exit_reraise = False
  10. # Controls whether or not early `exit()` calls should be made
  11. def maybe_exit(rc):
  12. if maybe_exit_should_exit:
  13. sys.exit(rc)
  14. if maybe_exit_reraise:
  15. e = sys.exc_info()[1]
  16. if e:
  17. raise e
  18. def maybe_exit_config(should_exit: bool = True, should_reraise: bool = False):
  19. global maybe_exit_should_exit
  20. global maybe_exit_reraise
  21. maybe_exit_should_exit = should_exit
  22. maybe_exit_reraise = should_reraise
  23. def cached_get(*args, **kwargs):
  24. import requests_cache
  25. session = requests_cache.CachedSession(Path('~/.local/qmk/qmk_requests.sqlite').expanduser(), expire_after=300, cache_control=True)
  26. return session.get(*args, **kwargs)
  27. def download_with_progress(url, filename):
  28. import requests
  29. import tqdm
  30. response = requests.get(url, stream=True)
  31. total_size = int(response.headers.get('content-length', 0))
  32. with tqdm.tqdm(desc=filename, total=total_size, unit='B', unit_scale=True) as pbar:
  33. with open(filename, 'wb') as file:
  34. for data in response.iter_content(1024):
  35. file.write(data)
  36. pbar.update(len(data))
  37. @contextlib.contextmanager
  38. def parallelize():
  39. """Returns a function that can be used in place of a map() call.
  40. Attempts to use `mpire`, falling back to `multiprocessing` if it's not
  41. available. If parallelization is not requested, returns the original map()
  42. function.
  43. """
  44. # Work out if we've already got a config value for parallel searching
  45. if cli.config.user.parallel_search is None:
  46. parallel_search = True
  47. else:
  48. parallel_search = cli.config.user.parallel_search
  49. # Non-parallel searches use `map()`
  50. if not parallel_search:
  51. yield map
  52. return
  53. # Prefer mpire's `WorkerPool` if it's available
  54. with contextlib.suppress(ImportError):
  55. from mpire import WorkerPool
  56. from mpire.utils import make_single_arguments
  57. with WorkerPool() as pool:
  58. def _worker(func, *args):
  59. # Ensure we don't unpack tuples -- mpire's `WorkerPool` tries to do so normally so we tell it not to.
  60. for r in pool.imap_unordered(func, make_single_arguments(*args, generator=False), progress_bar=True):
  61. yield r
  62. yield _worker
  63. return
  64. # Otherwise fall back to multiprocessing's `Pool`
  65. with multiprocessing.Pool() as pool:
  66. yield pool.imap_unordered
  67. def parallel_map(*args, **kwargs):
  68. """Effectively runs `map()` but executes it in parallel if necessary.
  69. """
  70. with parallelize() as map_fn:
  71. # This needs to be enclosed in a `list()` as some implementations return
  72. # a generator function, which means the scope of the pool is closed off
  73. # before the results are returned. Returning a list ensures results are
  74. # materialised before any worker pool is shut down.
  75. return list(map_fn(*args, **kwargs))