All patches and comments are welcome. Please squash your changes to logical
commits before using git-format-patch and git-send-email to
patches@git.madduck.net.
If you'd read over the Git project's submission guidelines and adhered to them,
I'd be especially grateful.
2 Formatting many files at once via multiprocessing. Contains entrypoint and utilities.
4 NOTE: this module is only imported if we need to format several files at once.
13 from concurrent.futures import Executor, ProcessPoolExecutor, ThreadPoolExecutor
14 from multiprocessing import Manager
15 from pathlib import Path
16 from typing import Any, Iterable, Optional, Set
18 from mypy_extensions import mypyc_attr
20 from black import WriteBack, format_file_in_place
21 from black.cache import Cache
22 from black.mode import Mode
23 from black.output import err
24 from black.report import Changed, Report
27 def maybe_install_uvloop() -> None:
28 """If our environment has uvloop installed we use it.
30 This is called only from command-line entry points to avoid
31 interfering with the parent process if Black is used as a library.
41 def cancel(tasks: Iterable["asyncio.Task[Any]"]) -> None:
42 """asyncio signal handler that cancels all `tasks` and reports to stderr."""
48 def shutdown(loop: asyncio.AbstractEventLoop) -> None:
49 """Cancel all pending tasks on `loop`, wait for them, and close the loop."""
51 # This part is borrowed from asyncio/runners.py in Python 3.7b2.
52 to_cancel = [task for task in asyncio.all_tasks(loop) if not task.done()]
56 for task in to_cancel:
58 loop.run_until_complete(asyncio.gather(*to_cancel, return_exceptions=True))
60 # `concurrent.futures.Future` objects cannot be cancelled once they
61 # are already running. There might be some when the `shutdown()` happened.
62 # Silence their logger's spew about the event loop being closed.
63 cf_logger = logging.getLogger("concurrent.futures")
64 cf_logger.setLevel(logging.CRITICAL)
68 # diff-shades depends on being to monkeypatch this function to operate. I know it's
69 # not ideal, but this shouldn't cause any issues ... hopefully. ~ichard26
70 @mypyc_attr(patchable=True)
74 write_back: WriteBack,
77 workers: Optional[int],
79 """Reformat multiple files using a ProcessPoolExecutor."""
80 maybe_install_uvloop()
84 workers = int(os.environ.get("BLACK_NUM_WORKERS", 0))
85 workers = workers or os.cpu_count() or 1
86 if sys.platform == "win32":
87 # Work around https://bugs.python.org/issue26903
88 workers = min(workers, 60)
90 executor = ProcessPoolExecutor(max_workers=workers)
91 except (ImportError, NotImplementedError, OSError):
92 # we arrive here if the underlying system does not support multi-processing
93 # like in AWS Lambda or Termux, in which case we gracefully fallback to
94 # a ThreadPoolExecutor with just a single worker (more workers would not do us
95 # any good due to the Global Interpreter Lock)
96 executor = ThreadPoolExecutor(max_workers=1)
98 loop = asyncio.new_event_loop()
99 asyncio.set_event_loop(loop)
101 loop.run_until_complete(
105 write_back=write_back,
116 asyncio.set_event_loop(None)
117 if executor is not None:
121 async def schedule_formatting(
124 write_back: WriteBack,
127 loop: asyncio.AbstractEventLoop,
128 executor: "Executor",
130 """Run formatting of `sources` in parallel using the provided `executor`.
132 (Use ProcessPoolExecutors for actual parallelism.)
134 `write_back`, `fast`, and `mode` options are passed to
135 :func:`format_file_in_place`.
137 cache = Cache.read(mode)
138 if write_back not in (WriteBack.DIFF, WriteBack.COLOR_DIFF):
139 sources, cached = cache.filtered_cached(sources)
140 for src in sorted(cached):
141 report.done(src, Changed.CACHED)
146 sources_to_cache = []
148 if write_back in (WriteBack.DIFF, WriteBack.COLOR_DIFF):
149 # For diff output, we need locks to ensure we don't interleave output
150 # from different processes.
152 lock = manager.Lock()
154 asyncio.ensure_future(
155 loop.run_in_executor(
156 executor, format_file_in_place, src, fast, mode, write_back, lock
159 for src in sorted(sources)
161 pending = tasks.keys()
163 loop.add_signal_handler(signal.SIGINT, cancel, pending)
164 loop.add_signal_handler(signal.SIGTERM, cancel, pending)
165 except NotImplementedError:
166 # There are no good alternatives for these on Windows.
169 done, _ = await asyncio.wait(pending, return_when=asyncio.FIRST_COMPLETED)
171 src = tasks.pop(task)
173 cancelled.append(task)
174 elif exc := task.exception():
176 traceback.print_exception(type(exc), exc, exc.__traceback__)
177 report.failed(src, str(exc))
179 changed = Changed.YES if task.result() else Changed.NO
180 # If the file was written back or was successfully checked as
181 # well-formatted, store this information in the cache.
182 if write_back is WriteBack.YES or (
183 write_back is WriteBack.CHECK and changed is Changed.NO
185 sources_to_cache.append(src)
186 report.done(src, changed)
188 await asyncio.gather(*cancelled, return_exceptions=True)
190 cache.write(sources_to_cache)