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.
12 from concurrent.futures import Executor, ProcessPoolExecutor, ThreadPoolExecutor
13 from multiprocessing import Manager
14 from pathlib import Path
15 from typing import Any, Iterable, Optional, Set
17 from mypy_extensions import mypyc_attr
19 from black import WriteBack, format_file_in_place
20 from black.cache import Cache, filter_cached, read_cache, write_cache
21 from black.mode import Mode
22 from black.output import err
23 from black.report import Changed, Report
26 def maybe_install_uvloop() -> None:
27 """If our environment has uvloop installed we use it.
29 This is called only from command-line entry points to avoid
30 interfering with the parent process if Black is used as a library.
40 def cancel(tasks: Iterable["asyncio.Task[Any]"]) -> None:
41 """asyncio signal handler that cancels all `tasks` and reports to stderr."""
47 def shutdown(loop: asyncio.AbstractEventLoop) -> None:
48 """Cancel all pending tasks on `loop`, wait for them, and close the loop."""
50 if sys.version_info[:2] >= (3, 7):
51 all_tasks = asyncio.all_tasks
53 all_tasks = asyncio.Task.all_tasks
54 # This part is borrowed from asyncio/runners.py in Python 3.7b2.
55 to_cancel = [task for task in all_tasks(loop) if not task.done()]
59 for task in to_cancel:
61 if sys.version_info >= (3, 7):
62 loop.run_until_complete(asyncio.gather(*to_cancel, return_exceptions=True))
64 loop.run_until_complete(
65 asyncio.gather(*to_cancel, loop=loop, return_exceptions=True)
68 # `concurrent.futures.Future` objects cannot be cancelled once they
69 # are already running. There might be some when the `shutdown()` happened.
70 # Silence their logger's spew about the event loop being closed.
71 cf_logger = logging.getLogger("concurrent.futures")
72 cf_logger.setLevel(logging.CRITICAL)
76 # diff-shades depends on being to monkeypatch this function to operate. I know it's
77 # not ideal, but this shouldn't cause any issues ... hopefully. ~ichard26
78 @mypyc_attr(patchable=True)
82 write_back: WriteBack,
85 workers: Optional[int],
87 """Reformat multiple files using a ProcessPoolExecutor."""
88 maybe_install_uvloop()
92 workers = os.cpu_count() or 1
93 if sys.platform == "win32":
94 # Work around https://bugs.python.org/issue26903
95 workers = min(workers, 60)
97 executor = ProcessPoolExecutor(max_workers=workers)
98 except (ImportError, NotImplementedError, OSError):
99 # we arrive here if the underlying system does not support multi-processing
100 # like in AWS Lambda or Termux, in which case we gracefully fallback to
101 # a ThreadPoolExecutor with just a single worker (more workers would not do us
102 # any good due to the Global Interpreter Lock)
103 executor = ThreadPoolExecutor(max_workers=1)
105 loop = asyncio.new_event_loop()
106 asyncio.set_event_loop(loop)
108 loop.run_until_complete(
112 write_back=write_back,
123 asyncio.set_event_loop(None)
124 if executor is not None:
128 async def schedule_formatting(
131 write_back: WriteBack,
134 loop: asyncio.AbstractEventLoop,
135 executor: "Executor",
137 """Run formatting of `sources` in parallel using the provided `executor`.
139 (Use ProcessPoolExecutors for actual parallelism.)
141 `write_back`, `fast`, and `mode` options are passed to
142 :func:`format_file_in_place`.
145 if write_back not in (WriteBack.DIFF, WriteBack.COLOR_DIFF):
146 cache = read_cache(mode)
147 sources, cached = filter_cached(cache, sources)
148 for src in sorted(cached):
149 report.done(src, Changed.CACHED)
154 sources_to_cache = []
156 if write_back in (WriteBack.DIFF, WriteBack.COLOR_DIFF):
157 # For diff output, we need locks to ensure we don't interleave output
158 # from different processes.
160 lock = manager.Lock()
162 asyncio.ensure_future(
163 loop.run_in_executor(
164 executor, format_file_in_place, src, fast, mode, write_back, lock
167 for src in sorted(sources)
169 pending = tasks.keys()
171 loop.add_signal_handler(signal.SIGINT, cancel, pending)
172 loop.add_signal_handler(signal.SIGTERM, cancel, pending)
173 except NotImplementedError:
174 # There are no good alternatives for these on Windows.
177 done, _ = await asyncio.wait(pending, return_when=asyncio.FIRST_COMPLETED)
179 src = tasks.pop(task)
181 cancelled.append(task)
182 elif task.exception():
183 report.failed(src, str(task.exception()))
185 changed = Changed.YES if task.result() else Changed.NO
186 # If the file was written back or was successfully checked as
187 # well-formatted, store this information in the cache.
188 if write_back is WriteBack.YES or (
189 write_back is WriteBack.CHECK and changed is Changed.NO
191 sources_to_cache.append(src)
192 report.done(src, changed)
194 if sys.version_info >= (3, 7):
195 await asyncio.gather(*cancelled, return_exceptions=True)
197 await asyncio.gather(*cancelled, loop=loop, return_exceptions=True)
199 write_cache(cache, sources_to_cache, mode)