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 from concurrent.futures import Executor, ThreadPoolExecutor, ProcessPoolExecutor
3 from contextlib import contextmanager
4 from datetime import datetime
7 from multiprocessing import Manager, freeze_support
9 from pathlib import Path
29 from dataclasses import replace
32 from black.const import DEFAULT_LINE_LENGTH, DEFAULT_INCLUDES, DEFAULT_EXCLUDES
33 from black.const import STDIN_PLACEHOLDER
34 from black.nodes import STARS, syms, is_simple_decorator_expression
35 from black.lines import Line, EmptyLineTracker
36 from black.linegen import transform_line, LineGenerator, LN
37 from black.comments import normalize_fmt_off
38 from black.mode import Mode, TargetVersion
39 from black.mode import Feature, supports_feature, VERSION_TO_FEATURES
40 from black.cache import read_cache, write_cache, get_cache_info, filter_cached, Cache
41 from black.concurrency import cancel, shutdown, maybe_install_uvloop
42 from black.output import dump_to_file, diff, color_diff, out, err
43 from black.report import Report, Changed
44 from black.files import find_project_root, find_pyproject_toml, parse_pyproject_toml
45 from black.files import gen_python_files, get_gitignore, normalize_path_maybe_ignore
46 from black.files import wrap_stream_for_windows
47 from black.parsing import InvalidInput # noqa F401
48 from black.parsing import lib2to3_parse, parse_ast, stringify_ast
52 from blib2to3.pytree import Node, Leaf
53 from blib2to3.pgen2 import token
55 from _black_version import version as __version__
63 class NothingChanged(UserWarning):
64 """Raised when reformatted code is the same as source."""
67 class WriteBack(Enum):
75 def from_configuration(
76 cls, *, check: bool, diff: bool, color: bool = False
78 if check and not diff:
84 return cls.DIFF if diff else cls.YES
87 # Legacy name, left for integrations.
91 def read_pyproject_toml(
92 ctx: click.Context, param: click.Parameter, value: Optional[str]
94 """Inject Black configuration from "pyproject.toml" into defaults in `ctx`.
96 Returns the path to a successfully found and read configuration file, None
100 value = find_pyproject_toml(ctx.params.get("src", ()))
105 config = parse_pyproject_toml(value)
106 except (OSError, ValueError) as e:
107 raise click.FileError(
108 filename=value, hint=f"Error reading configuration file: {e}"
114 # Sanitize the values to be Click friendly. For more information please see:
115 # https://github.com/psf/black/issues/1458
116 # https://github.com/pallets/click/issues/1567
118 k: str(v) if not isinstance(v, (list, dict)) else v
119 for k, v in config.items()
122 target_version = config.get("target_version")
123 if target_version is not None and not isinstance(target_version, list):
124 raise click.BadOptionUsage(
125 "target-version", "Config key target-version must be a list"
128 default_map: Dict[str, Any] = {}
130 default_map.update(ctx.default_map)
131 default_map.update(config)
133 ctx.default_map = default_map
137 def target_version_option_callback(
138 c: click.Context, p: Union[click.Option, click.Parameter], v: Tuple[str, ...]
139 ) -> List[TargetVersion]:
140 """Compute the target versions from a --target-version flag.
142 This is its own function because mypy couldn't infer the type correctly
143 when it was a lambda, causing mypyc trouble.
145 return [TargetVersion[val.upper()] for val in v]
148 def re_compile_maybe_verbose(regex: str) -> Pattern[str]:
149 """Compile a regular expression string in `regex`.
151 If it contains newlines, use verbose mode.
154 regex = "(?x)" + regex
155 compiled: Pattern[str] = re.compile(regex)
161 param: click.Parameter,
162 value: Optional[str],
163 ) -> Optional[Pattern]:
165 return re_compile_maybe_verbose(value) if value is not None else None
167 raise click.BadParameter("Not a valid regular expression")
170 @click.command(context_settings=dict(help_option_names=["-h", "--help"]))
171 @click.option("-c", "--code", type=str, help="Format the code passed in as a string.")
176 default=DEFAULT_LINE_LENGTH,
177 help="How many characters per line to allow.",
183 type=click.Choice([v.name.lower() for v in TargetVersion]),
184 callback=target_version_option_callback,
187 "Python versions that should be supported by Black's output. [default: per-file"
195 "Format all input files like typing stubs regardless of file extension (useful"
196 " when piping source on standard input)."
201 "--skip-string-normalization",
203 help="Don't normalize string quotes or prefixes.",
207 "--skip-magic-trailing-comma",
209 help="Don't use trailing commas as a reason to split lines.",
212 "--experimental-string-processing",
216 "Experimental option that performs more normalization on string literals."
217 " Currently disabled because it leads to some crashes."
224 "Don't write the files back, just return the status. Return code 0 means"
225 " nothing would change. Return code 1 means some files would be reformatted."
226 " Return code 123 means there was an internal error."
232 help="Don't write the files back, just output a diff for each file on stdout.",
235 "--color/--no-color",
237 help="Show colored diff. Only applies when `--diff` is given.",
242 help="If --fast given, skip temporary sanity checks. [default: --safe]",
245 "--required-version",
248 "Require a specific version of Black to be running (useful for unifying results"
249 " across many environments e.g. with a pyproject.toml file)."
255 default=DEFAULT_INCLUDES,
256 callback=validate_regex,
258 "A regular expression that matches files and directories that should be"
259 " included on recursive searches. An empty value means all files are included"
260 " regardless of the name. Use forward slashes for directories on all platforms"
261 " (Windows, too). Exclusions are calculated first, inclusions later."
268 callback=validate_regex,
270 "A regular expression that matches files and directories that should be"
271 " excluded on recursive searches. An empty value means no paths are excluded."
272 " Use forward slashes for directories on all platforms (Windows, too)."
273 " Exclusions are calculated first, inclusions later. [default:"
274 f" {DEFAULT_EXCLUDES}]"
281 callback=validate_regex,
283 "Like --exclude, but adds additional files and directories on top of the"
284 " excluded ones. (Useful if you simply want to add to the default)"
290 callback=validate_regex,
292 "Like --exclude, but files and directories matching this regex will be "
293 "excluded even when they are passed explicitly as arguments."
300 "The name of the file when passing it through stdin. Useful to make "
301 "sure Black will respect --force-exclude option on some "
302 "editors that rely on using stdin."
310 "Don't emit non-error messages to stderr. Errors are still emitted; silence"
311 " those with 2>/dev/null."
319 "Also emit messages to stderr about files that were not changed or were ignored"
320 " due to exclusion patterns."
323 @click.version_option(version=__version__)
328 exists=True, file_okay=True, dir_okay=True, readable=True, allow_dash=True
343 callback=read_pyproject_toml,
344 help="Read configuration from FILE path.",
351 target_version: List[TargetVersion],
357 skip_string_normalization: bool,
358 skip_magic_trailing_comma: bool,
359 experimental_string_processing: bool,
362 required_version: str,
364 exclude: Optional[Pattern],
365 extend_exclude: Optional[Pattern],
366 force_exclude: Optional[Pattern],
367 stdin_filename: Optional[str],
368 src: Tuple[str, ...],
369 config: Optional[str],
371 """The uncompromising code formatter."""
372 if config and verbose:
373 out(f"Using configuration from {config}.", bold=False, fg="blue")
375 error_msg = "Oh no! 💥 💔 💥"
376 if required_version and required_version != __version__:
378 f"{error_msg} The required version `{required_version}` does not match"
379 f" the running version `{__version__}`!"
383 write_back = WriteBack.from_configuration(check=check, diff=diff, color=color)
385 versions = set(target_version)
387 # We'll autodetect later.
390 target_versions=versions,
391 line_length=line_length,
393 string_normalization=not skip_string_normalization,
394 magic_trailing_comma=not skip_magic_trailing_comma,
395 experimental_string_processing=experimental_string_processing,
399 # Run in quiet mode by default with -c; the extra output isn't useful.
400 # You can still pass -v to get verbose output.
403 report = Report(check=check, diff=diff, quiet=quiet, verbose=verbose)
407 content=code, fast=fast, write_back=write_back, mode=mode, report=report
410 sources = get_sources(
417 extend_exclude=extend_exclude,
418 force_exclude=force_exclude,
420 stdin_filename=stdin_filename,
425 "No Python files are present to be formatted. Nothing to do 😴",
431 if len(sources) == 1:
435 write_back=write_back,
443 write_back=write_back,
448 if verbose or not quiet:
449 out(error_msg if report.return_code else "All done! ✨ 🍰 ✨")
451 click.echo(str(report), err=True)
452 ctx.exit(report.return_code)
458 src: Tuple[str, ...],
461 include: Pattern[str],
462 exclude: Optional[Pattern[str]],
463 extend_exclude: Optional[Pattern[str]],
464 force_exclude: Optional[Pattern[str]],
466 stdin_filename: Optional[str],
468 """Compute the set of files to be formatted."""
470 root = find_project_root(src)
471 sources: Set[Path] = set()
472 path_empty(src, "No Path provided. Nothing to do 😴", quiet, verbose, ctx)
475 exclude = re_compile_maybe_verbose(DEFAULT_EXCLUDES)
476 gitignore = get_gitignore(root)
481 if s == "-" and stdin_filename:
482 p = Path(stdin_filename)
488 if is_stdin or p.is_file():
489 normalized_path = normalize_path_maybe_ignore(p, root, report)
490 if normalized_path is None:
493 normalized_path = "/" + normalized_path
494 # Hard-exclude any files that matches the `--force-exclude` regex.
496 force_exclude_match = force_exclude.search(normalized_path)
498 force_exclude_match = None
499 if force_exclude_match and force_exclude_match.group(0):
500 report.path_ignored(p, "matches the --force-exclude regular expression")
504 p = Path(f"{STDIN_PLACEHOLDER}{str(p)}")
523 err(f"invalid path: {s}")
528 src: Sized, msg: str, quiet: bool, verbose: bool, ctx: click.Context
531 Exit if there is no `src` provided for formatting
534 if verbose or not quiet:
540 content: str, fast: bool, write_back: WriteBack, mode: Mode, report: Report
543 Reformat and print out `content` without spawning child processes.
544 Similar to `reformat_one`, but for string content.
546 `fast`, `write_back`, and `mode` options are passed to
547 :func:`format_file_in_place` or :func:`format_stdin_to_stdout`.
549 path = Path("<string>")
552 if format_stdin_to_stdout(
553 content=content, fast=fast, write_back=write_back, mode=mode
555 changed = Changed.YES
556 report.done(path, changed)
557 except Exception as exc:
559 traceback.print_exc()
560 report.failed(path, str(exc))
564 src: Path, fast: bool, write_back: WriteBack, mode: Mode, report: "Report"
566 """Reformat a single file under `src` without spawning child processes.
568 `fast`, `write_back`, and `mode` options are passed to
569 :func:`format_file_in_place` or :func:`format_stdin_to_stdout`.
576 elif str(src).startswith(STDIN_PLACEHOLDER):
578 # Use the original name again in case we want to print something
580 src = Path(str(src)[len(STDIN_PLACEHOLDER) :])
585 if src.suffix == ".pyi":
586 mode = replace(mode, is_pyi=True)
587 if format_stdin_to_stdout(fast=fast, write_back=write_back, mode=mode):
588 changed = Changed.YES
591 if write_back not in (WriteBack.DIFF, WriteBack.COLOR_DIFF):
592 cache = read_cache(mode)
593 res_src = src.resolve()
594 res_src_s = str(res_src)
595 if res_src_s in cache and cache[res_src_s] == get_cache_info(res_src):
596 changed = Changed.CACHED
597 if changed is not Changed.CACHED and format_file_in_place(
598 src, fast=fast, write_back=write_back, mode=mode
600 changed = Changed.YES
601 if (write_back is WriteBack.YES and changed is not Changed.CACHED) or (
602 write_back is WriteBack.CHECK and changed is Changed.NO
604 write_cache(cache, [src], mode)
605 report.done(src, changed)
606 except Exception as exc:
608 traceback.print_exc()
609 report.failed(src, str(exc))
613 sources: Set[Path], fast: bool, write_back: WriteBack, mode: Mode, report: "Report"
615 """Reformat multiple files using a ProcessPoolExecutor."""
617 loop = asyncio.get_event_loop()
618 worker_count = os.cpu_count()
619 if sys.platform == "win32":
620 # Work around https://bugs.python.org/issue26903
621 worker_count = min(worker_count, 60)
623 executor = ProcessPoolExecutor(max_workers=worker_count)
624 except (ImportError, OSError):
625 # we arrive here if the underlying system does not support multi-processing
626 # like in AWS Lambda or Termux, in which case we gracefully fallback to
627 # a ThreadPoolExecutor with just a single worker (more workers would not do us
628 # any good due to the Global Interpreter Lock)
629 executor = ThreadPoolExecutor(max_workers=1)
632 loop.run_until_complete(
636 write_back=write_back,
645 if executor is not None:
649 async def schedule_formatting(
652 write_back: WriteBack,
655 loop: asyncio.AbstractEventLoop,
658 """Run formatting of `sources` in parallel using the provided `executor`.
660 (Use ProcessPoolExecutors for actual parallelism.)
662 `write_back`, `fast`, and `mode` options are passed to
663 :func:`format_file_in_place`.
666 if write_back not in (WriteBack.DIFF, WriteBack.COLOR_DIFF):
667 cache = read_cache(mode)
668 sources, cached = filter_cached(cache, sources)
669 for src in sorted(cached):
670 report.done(src, Changed.CACHED)
675 sources_to_cache = []
677 if write_back in (WriteBack.DIFF, WriteBack.COLOR_DIFF):
678 # For diff output, we need locks to ensure we don't interleave output
679 # from different processes.
681 lock = manager.Lock()
683 asyncio.ensure_future(
684 loop.run_in_executor(
685 executor, format_file_in_place, src, fast, mode, write_back, lock
688 for src in sorted(sources)
690 pending = tasks.keys()
692 loop.add_signal_handler(signal.SIGINT, cancel, pending)
693 loop.add_signal_handler(signal.SIGTERM, cancel, pending)
694 except NotImplementedError:
695 # There are no good alternatives for these on Windows.
698 done, _ = await asyncio.wait(pending, return_when=asyncio.FIRST_COMPLETED)
700 src = tasks.pop(task)
702 cancelled.append(task)
703 elif task.exception():
704 report.failed(src, str(task.exception()))
706 changed = Changed.YES if task.result() else Changed.NO
707 # If the file was written back or was successfully checked as
708 # well-formatted, store this information in the cache.
709 if write_back is WriteBack.YES or (
710 write_back is WriteBack.CHECK and changed is Changed.NO
712 sources_to_cache.append(src)
713 report.done(src, changed)
715 await asyncio.gather(*cancelled, loop=loop, return_exceptions=True)
717 write_cache(cache, sources_to_cache, mode)
720 def format_file_in_place(
724 write_back: WriteBack = WriteBack.NO,
725 lock: Any = None, # multiprocessing.Manager().Lock() is some crazy proxy
727 """Format file under `src` path. Return True if changed.
729 If `write_back` is DIFF, write a diff to stdout. If it is YES, write reformatted
731 `mode` and `fast` options are passed to :func:`format_file_contents`.
733 if src.suffix == ".pyi":
734 mode = replace(mode, is_pyi=True)
736 then = datetime.utcfromtimestamp(src.stat().st_mtime)
737 with open(src, "rb") as buf:
738 src_contents, encoding, newline = decode_bytes(buf.read())
740 dst_contents = format_file_contents(src_contents, fast=fast, mode=mode)
741 except NothingChanged:
744 if write_back == WriteBack.YES:
745 with open(src, "w", encoding=encoding, newline=newline) as f:
746 f.write(dst_contents)
747 elif write_back in (WriteBack.DIFF, WriteBack.COLOR_DIFF):
748 now = datetime.utcnow()
749 src_name = f"{src}\t{then} +0000"
750 dst_name = f"{src}\t{now} +0000"
751 diff_contents = diff(src_contents, dst_contents, src_name, dst_name)
753 if write_back == WriteBack.COLOR_DIFF:
754 diff_contents = color_diff(diff_contents)
756 with lock or nullcontext():
757 f = io.TextIOWrapper(
763 f = wrap_stream_for_windows(f)
764 f.write(diff_contents)
770 def format_stdin_to_stdout(
773 content: Optional[str] = None,
774 write_back: WriteBack = WriteBack.NO,
777 """Format file on stdin. Return True if changed.
779 If content is None, it's read from sys.stdin.
781 If `write_back` is YES, write reformatted code back to stdout. If it is DIFF,
782 write a diff to stdout. The `mode` argument is passed to
783 :func:`format_file_contents`.
785 then = datetime.utcnow()
788 src, encoding, newline = decode_bytes(sys.stdin.buffer.read())
790 src, encoding, newline = content, "utf-8", ""
794 dst = format_file_contents(src, fast=fast, mode=mode)
797 except NothingChanged:
801 f = io.TextIOWrapper(
802 sys.stdout.buffer, encoding=encoding, newline=newline, write_through=True
804 if write_back == WriteBack.YES:
805 # Make sure there's a newline after the content
806 if dst and dst[-1] != "\n":
809 elif write_back in (WriteBack.DIFF, WriteBack.COLOR_DIFF):
810 now = datetime.utcnow()
811 src_name = f"STDIN\t{then} +0000"
812 dst_name = f"STDOUT\t{now} +0000"
813 d = diff(src, dst, src_name, dst_name)
814 if write_back == WriteBack.COLOR_DIFF:
816 f = wrap_stream_for_windows(f)
821 def format_file_contents(src_contents: str, *, fast: bool, mode: Mode) -> FileContent:
822 """Reformat contents of a file and return new contents.
824 If `fast` is False, additionally confirm that the reformatted code is
825 valid by calling :func:`assert_equivalent` and :func:`assert_stable` on it.
826 `mode` is passed to :func:`format_str`.
828 if not src_contents.strip():
831 dst_contents = format_str(src_contents, mode=mode)
832 if src_contents == dst_contents:
836 assert_equivalent(src_contents, dst_contents)
838 # Forced second pass to work around optional trailing commas (becoming
839 # forced trailing commas on pass 2) interacting differently with optional
840 # parentheses. Admittedly ugly.
841 dst_contents_pass2 = format_str(dst_contents, mode=mode)
842 if dst_contents != dst_contents_pass2:
843 dst_contents = dst_contents_pass2
844 assert_equivalent(src_contents, dst_contents, pass_num=2)
845 assert_stable(src_contents, dst_contents, mode=mode)
846 # Note: no need to explicitly call `assert_stable` if `dst_contents` was
847 # the same as `dst_contents_pass2`.
851 def format_str(src_contents: str, *, mode: Mode) -> FileContent:
852 """Reformat a string and return new contents.
854 `mode` determines formatting options, such as how many characters per line are
858 >>> print(black.format_str("def f(arg:str='')->None:...", mode=black.Mode()))
859 def f(arg: str = "") -> None:
862 A more complex example:
865 ... black.format_str(
866 ... "def f(arg:str='')->None: hey",
868 ... target_versions={black.TargetVersion.PY36},
870 ... string_normalization=False,
881 src_node = lib2to3_parse(src_contents.lstrip(), mode.target_versions)
883 future_imports = get_future_imports(src_node)
884 if mode.target_versions:
885 versions = mode.target_versions
887 versions = detect_target_versions(src_node)
888 normalize_fmt_off(src_node)
889 lines = LineGenerator(
891 remove_u_prefix="unicode_literals" in future_imports
892 or supports_feature(versions, Feature.UNICODE_LITERALS),
894 elt = EmptyLineTracker(is_pyi=mode.is_pyi)
895 empty_line = Line(mode=mode)
897 split_line_features = {
899 for feature in {Feature.TRAILING_COMMA_IN_CALL, Feature.TRAILING_COMMA_IN_DEF}
900 if supports_feature(versions, feature)
902 for current_line in lines.visit(src_node):
903 dst_contents.append(str(empty_line) * after)
904 before, after = elt.maybe_empty_lines(current_line)
905 dst_contents.append(str(empty_line) * before)
906 for line in transform_line(
907 current_line, mode=mode, features=split_line_features
909 dst_contents.append(str(line))
910 return "".join(dst_contents)
913 def decode_bytes(src: bytes) -> Tuple[FileContent, Encoding, NewLine]:
914 """Return a tuple of (decoded_contents, encoding, newline).
916 `newline` is either CRLF or LF but `decoded_contents` is decoded with
917 universal newlines (i.e. only contains LF).
919 srcbuf = io.BytesIO(src)
920 encoding, lines = tokenize.detect_encoding(srcbuf.readline)
922 return "", encoding, "\n"
924 newline = "\r\n" if b"\r\n" == lines[0][-2:] else "\n"
926 with io.TextIOWrapper(srcbuf, encoding) as tiow:
927 return tiow.read(), encoding, newline
930 def get_features_used(node: Node) -> Set[Feature]:
931 """Return a set of (relatively) new Python features used in this file.
933 Currently looking for:
935 - underscores in numeric literals;
936 - trailing commas after * or ** in function signatures and calls;
937 - positional only arguments in function signatures and lambdas;
938 - assignment expression;
939 - relaxed decorator syntax;
941 features: Set[Feature] = set()
942 for n in node.pre_order():
943 if n.type == token.STRING:
944 value_head = n.value[:2] # type: ignore
945 if value_head in {'f"', 'F"', "f'", "F'", "rf", "fr", "RF", "FR"}:
946 features.add(Feature.F_STRINGS)
948 elif n.type == token.NUMBER:
949 if "_" in n.value: # type: ignore
950 features.add(Feature.NUMERIC_UNDERSCORES)
952 elif n.type == token.SLASH:
953 if n.parent and n.parent.type in {syms.typedargslist, syms.arglist}:
954 features.add(Feature.POS_ONLY_ARGUMENTS)
956 elif n.type == token.COLONEQUAL:
957 features.add(Feature.ASSIGNMENT_EXPRESSIONS)
959 elif n.type == syms.decorator:
960 if len(n.children) > 1 and not is_simple_decorator_expression(
963 features.add(Feature.RELAXED_DECORATORS)
966 n.type in {syms.typedargslist, syms.arglist}
968 and n.children[-1].type == token.COMMA
970 if n.type == syms.typedargslist:
971 feature = Feature.TRAILING_COMMA_IN_DEF
973 feature = Feature.TRAILING_COMMA_IN_CALL
975 for ch in n.children:
977 features.add(feature)
979 if ch.type == syms.argument:
980 for argch in ch.children:
981 if argch.type in STARS:
982 features.add(feature)
987 def detect_target_versions(node: Node) -> Set[TargetVersion]:
988 """Detect the version to target based on the nodes used."""
989 features = get_features_used(node)
991 version for version in TargetVersion if features <= VERSION_TO_FEATURES[version]
995 def get_future_imports(node: Node) -> Set[str]:
996 """Return a set of __future__ imports in the file."""
997 imports: Set[str] = set()
999 def get_imports_from_children(children: List[LN]) -> Generator[str, None, None]:
1000 for child in children:
1001 if isinstance(child, Leaf):
1002 if child.type == token.NAME:
1005 elif child.type == syms.import_as_name:
1006 orig_name = child.children[0]
1007 assert isinstance(orig_name, Leaf), "Invalid syntax parsing imports"
1008 assert orig_name.type == token.NAME, "Invalid syntax parsing imports"
1009 yield orig_name.value
1011 elif child.type == syms.import_as_names:
1012 yield from get_imports_from_children(child.children)
1015 raise AssertionError("Invalid syntax parsing imports")
1017 for child in node.children:
1018 if child.type != syms.simple_stmt:
1021 first_child = child.children[0]
1022 if isinstance(first_child, Leaf):
1023 # Continue looking if we see a docstring; otherwise stop.
1025 len(child.children) == 2
1026 and first_child.type == token.STRING
1027 and child.children[1].type == token.NEWLINE
1033 elif first_child.type == syms.import_from:
1034 module_name = first_child.children[1]
1035 if not isinstance(module_name, Leaf) or module_name.value != "__future__":
1038 imports |= set(get_imports_from_children(first_child.children[3:]))
1045 def assert_equivalent(src: str, dst: str, *, pass_num: int = 1) -> None:
1046 """Raise AssertionError if `src` and `dst` aren't equivalent."""
1048 src_ast = parse_ast(src)
1049 except Exception as exc:
1050 raise AssertionError(
1051 "cannot use --safe with this file; failed to parse source file. AST"
1052 f" error message: {exc}"
1056 dst_ast = parse_ast(dst)
1057 except Exception as exc:
1058 log = dump_to_file("".join(traceback.format_tb(exc.__traceback__)), dst)
1059 raise AssertionError(
1060 f"INTERNAL ERROR: Black produced invalid code on pass {pass_num}: {exc}. "
1061 "Please report a bug on https://github.com/psf/black/issues. "
1062 f"This invalid output might be helpful: {log}"
1065 src_ast_str = "\n".join(stringify_ast(src_ast))
1066 dst_ast_str = "\n".join(stringify_ast(dst_ast))
1067 if src_ast_str != dst_ast_str:
1068 log = dump_to_file(diff(src_ast_str, dst_ast_str, "src", "dst"))
1069 raise AssertionError(
1070 "INTERNAL ERROR: Black produced code that is not equivalent to the"
1071 f" source on pass {pass_num}. Please report a bug on "
1072 f"https://github.com/psf/black/issues. This diff might be helpful: {log}"
1076 def assert_stable(src: str, dst: str, mode: Mode) -> None:
1077 """Raise AssertionError if `dst` reformats differently the second time."""
1078 newdst = format_str(dst, mode=mode)
1082 diff(src, dst, "source", "first pass"),
1083 diff(dst, newdst, "first pass", "second pass"),
1085 raise AssertionError(
1086 "INTERNAL ERROR: Black produced different code on the second pass of the"
1087 " formatter. Please report a bug on https://github.com/psf/black/issues."
1088 f" This diff might be helpful: {log}"
1093 def nullcontext() -> Iterator[None]:
1094 """Return an empty context manager.
1096 To be used like `nullcontext` in Python 3.7.
1101 def patch_click() -> None:
1102 """Make Click not crash on Python 3.6 with LANG=C.
1104 On certain misconfigured environments, Python 3 selects the ASCII encoding as the
1105 default which restricts paths that it can access during the lifetime of the
1106 application. Click refuses to work in this scenario by raising a RuntimeError.
1108 In case of Black the likelihood that non-ASCII characters are going to be used in
1109 file paths is minimal since it's Python source code. Moreover, this crash was
1110 spurious on Python 3.7 thanks to PEP 538 and PEP 540.
1113 from click import core
1114 from click import _unicodefun # type: ignore
1115 except ModuleNotFoundError:
1118 for module in (core, _unicodefun):
1119 if hasattr(module, "_verify_python3_env"):
1120 module._verify_python3_env = lambda: None # type: ignore
1121 if hasattr(module, "_verify_python_env"):
1122 module._verify_python_env = lambda: None # type: ignore
1125 def patched_main() -> None:
1126 maybe_install_uvloop()
1132 if __name__ == "__main__":