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
344 callback=read_pyproject_toml,
345 help="Read configuration from FILE path.",
352 target_version: List[TargetVersion],
358 skip_string_normalization: bool,
359 skip_magic_trailing_comma: bool,
360 experimental_string_processing: bool,
363 required_version: str,
365 exclude: Optional[Pattern],
366 extend_exclude: Optional[Pattern],
367 force_exclude: Optional[Pattern],
368 stdin_filename: Optional[str],
369 src: Tuple[str, ...],
370 config: Optional[str],
372 """The uncompromising code formatter."""
373 if config and verbose:
374 out(f"Using configuration from {config}.", bold=False, fg="blue")
376 error_msg = "Oh no! 💥 💔 💥"
377 if required_version and required_version != __version__:
379 f"{error_msg} The required version `{required_version}` does not match"
380 f" the running version `{__version__}`!"
384 write_back = WriteBack.from_configuration(check=check, diff=diff, color=color)
386 versions = set(target_version)
388 # We'll autodetect later.
391 target_versions=versions,
392 line_length=line_length,
394 string_normalization=not skip_string_normalization,
395 magic_trailing_comma=not skip_magic_trailing_comma,
396 experimental_string_processing=experimental_string_processing,
400 # Run in quiet mode by default with -c; the extra output isn't useful.
401 # You can still pass -v to get verbose output.
404 report = Report(check=check, diff=diff, quiet=quiet, verbose=verbose)
408 content=code, fast=fast, write_back=write_back, mode=mode, report=report
411 sources = get_sources(
418 extend_exclude=extend_exclude,
419 force_exclude=force_exclude,
421 stdin_filename=stdin_filename,
426 "No Python files are present to be formatted. Nothing to do 😴",
432 if len(sources) == 1:
436 write_back=write_back,
444 write_back=write_back,
449 if verbose or not quiet:
450 out(error_msg if report.return_code else "All done! ✨ 🍰 ✨")
452 click.echo(str(report), err=True)
453 ctx.exit(report.return_code)
459 src: Tuple[str, ...],
462 include: Pattern[str],
463 exclude: Optional[Pattern[str]],
464 extend_exclude: Optional[Pattern[str]],
465 force_exclude: Optional[Pattern[str]],
467 stdin_filename: Optional[str],
469 """Compute the set of files to be formatted."""
471 root = find_project_root(src)
472 sources: Set[Path] = set()
473 path_empty(src, "No Path provided. Nothing to do 😴", quiet, verbose, ctx)
476 exclude = re_compile_maybe_verbose(DEFAULT_EXCLUDES)
477 gitignore = get_gitignore(root)
482 if s == "-" and stdin_filename:
483 p = Path(stdin_filename)
489 if is_stdin or p.is_file():
490 normalized_path = normalize_path_maybe_ignore(p, root, report)
491 if normalized_path is None:
494 normalized_path = "/" + normalized_path
495 # Hard-exclude any files that matches the `--force-exclude` regex.
497 force_exclude_match = force_exclude.search(normalized_path)
499 force_exclude_match = None
500 if force_exclude_match and force_exclude_match.group(0):
501 report.path_ignored(p, "matches the --force-exclude regular expression")
505 p = Path(f"{STDIN_PLACEHOLDER}{str(p)}")
524 err(f"invalid path: {s}")
529 src: Sized, msg: str, quiet: bool, verbose: bool, ctx: click.Context
532 Exit if there is no `src` provided for formatting
535 if verbose or not quiet:
541 content: str, fast: bool, write_back: WriteBack, mode: Mode, report: Report
544 Reformat and print out `content` without spawning child processes.
545 Similar to `reformat_one`, but for string content.
547 `fast`, `write_back`, and `mode` options are passed to
548 :func:`format_file_in_place` or :func:`format_stdin_to_stdout`.
550 path = Path("<string>")
553 if format_stdin_to_stdout(
554 content=content, fast=fast, write_back=write_back, mode=mode
556 changed = Changed.YES
557 report.done(path, changed)
558 except Exception as exc:
560 traceback.print_exc()
561 report.failed(path, str(exc))
565 src: Path, fast: bool, write_back: WriteBack, mode: Mode, report: "Report"
567 """Reformat a single file under `src` without spawning child processes.
569 `fast`, `write_back`, and `mode` options are passed to
570 :func:`format_file_in_place` or :func:`format_stdin_to_stdout`.
577 elif str(src).startswith(STDIN_PLACEHOLDER):
579 # Use the original name again in case we want to print something
581 src = Path(str(src)[len(STDIN_PLACEHOLDER) :])
586 if src.suffix == ".pyi":
587 mode = replace(mode, is_pyi=True)
588 if format_stdin_to_stdout(fast=fast, write_back=write_back, mode=mode):
589 changed = Changed.YES
592 if write_back not in (WriteBack.DIFF, WriteBack.COLOR_DIFF):
593 cache = read_cache(mode)
594 res_src = src.resolve()
595 res_src_s = str(res_src)
596 if res_src_s in cache and cache[res_src_s] == get_cache_info(res_src):
597 changed = Changed.CACHED
598 if changed is not Changed.CACHED and format_file_in_place(
599 src, fast=fast, write_back=write_back, mode=mode
601 changed = Changed.YES
602 if (write_back is WriteBack.YES and changed is not Changed.CACHED) or (
603 write_back is WriteBack.CHECK and changed is Changed.NO
605 write_cache(cache, [src], mode)
606 report.done(src, changed)
607 except Exception as exc:
609 traceback.print_exc()
610 report.failed(src, str(exc))
614 sources: Set[Path], fast: bool, write_back: WriteBack, mode: Mode, report: "Report"
616 """Reformat multiple files using a ProcessPoolExecutor."""
618 loop = asyncio.get_event_loop()
619 worker_count = os.cpu_count()
620 if sys.platform == "win32":
621 # Work around https://bugs.python.org/issue26903
622 worker_count = min(worker_count, 60)
624 executor = ProcessPoolExecutor(max_workers=worker_count)
625 except (ImportError, OSError):
626 # we arrive here if the underlying system does not support multi-processing
627 # like in AWS Lambda or Termux, in which case we gracefully fallback to
628 # a ThreadPoolExecutor with just a single worker (more workers would not do us
629 # any good due to the Global Interpreter Lock)
630 executor = ThreadPoolExecutor(max_workers=1)
633 loop.run_until_complete(
637 write_back=write_back,
646 if executor is not None:
650 async def schedule_formatting(
653 write_back: WriteBack,
656 loop: asyncio.AbstractEventLoop,
659 """Run formatting of `sources` in parallel using the provided `executor`.
661 (Use ProcessPoolExecutors for actual parallelism.)
663 `write_back`, `fast`, and `mode` options are passed to
664 :func:`format_file_in_place`.
667 if write_back not in (WriteBack.DIFF, WriteBack.COLOR_DIFF):
668 cache = read_cache(mode)
669 sources, cached = filter_cached(cache, sources)
670 for src in sorted(cached):
671 report.done(src, Changed.CACHED)
676 sources_to_cache = []
678 if write_back in (WriteBack.DIFF, WriteBack.COLOR_DIFF):
679 # For diff output, we need locks to ensure we don't interleave output
680 # from different processes.
682 lock = manager.Lock()
684 asyncio.ensure_future(
685 loop.run_in_executor(
686 executor, format_file_in_place, src, fast, mode, write_back, lock
689 for src in sorted(sources)
691 pending = tasks.keys()
693 loop.add_signal_handler(signal.SIGINT, cancel, pending)
694 loop.add_signal_handler(signal.SIGTERM, cancel, pending)
695 except NotImplementedError:
696 # There are no good alternatives for these on Windows.
699 done, _ = await asyncio.wait(pending, return_when=asyncio.FIRST_COMPLETED)
701 src = tasks.pop(task)
703 cancelled.append(task)
704 elif task.exception():
705 report.failed(src, str(task.exception()))
707 changed = Changed.YES if task.result() else Changed.NO
708 # If the file was written back or was successfully checked as
709 # well-formatted, store this information in the cache.
710 if write_back is WriteBack.YES or (
711 write_back is WriteBack.CHECK and changed is Changed.NO
713 sources_to_cache.append(src)
714 report.done(src, changed)
716 await asyncio.gather(*cancelled, loop=loop, return_exceptions=True)
718 write_cache(cache, sources_to_cache, mode)
721 def format_file_in_place(
725 write_back: WriteBack = WriteBack.NO,
726 lock: Any = None, # multiprocessing.Manager().Lock() is some crazy proxy
728 """Format file under `src` path. Return True if changed.
730 If `write_back` is DIFF, write a diff to stdout. If it is YES, write reformatted
732 `mode` and `fast` options are passed to :func:`format_file_contents`.
734 if src.suffix == ".pyi":
735 mode = replace(mode, is_pyi=True)
737 then = datetime.utcfromtimestamp(src.stat().st_mtime)
738 with open(src, "rb") as buf:
739 src_contents, encoding, newline = decode_bytes(buf.read())
741 dst_contents = format_file_contents(src_contents, fast=fast, mode=mode)
742 except NothingChanged:
745 if write_back == WriteBack.YES:
746 with open(src, "w", encoding=encoding, newline=newline) as f:
747 f.write(dst_contents)
748 elif write_back in (WriteBack.DIFF, WriteBack.COLOR_DIFF):
749 now = datetime.utcnow()
750 src_name = f"{src}\t{then} +0000"
751 dst_name = f"{src}\t{now} +0000"
752 diff_contents = diff(src_contents, dst_contents, src_name, dst_name)
754 if write_back == WriteBack.COLOR_DIFF:
755 diff_contents = color_diff(diff_contents)
757 with lock or nullcontext():
758 f = io.TextIOWrapper(
764 f = wrap_stream_for_windows(f)
765 f.write(diff_contents)
771 def format_stdin_to_stdout(
774 content: Optional[str] = None,
775 write_back: WriteBack = WriteBack.NO,
778 """Format file on stdin. Return True if changed.
780 If content is None, it's read from sys.stdin.
782 If `write_back` is YES, write reformatted code back to stdout. If it is DIFF,
783 write a diff to stdout. The `mode` argument is passed to
784 :func:`format_file_contents`.
786 then = datetime.utcnow()
789 src, encoding, newline = decode_bytes(sys.stdin.buffer.read())
791 src, encoding, newline = content, "utf-8", ""
795 dst = format_file_contents(src, fast=fast, mode=mode)
798 except NothingChanged:
802 f = io.TextIOWrapper(
803 sys.stdout.buffer, encoding=encoding, newline=newline, write_through=True
805 if write_back == WriteBack.YES:
806 # Make sure there's a newline after the content
807 if dst and dst[-1] != "\n":
810 elif write_back in (WriteBack.DIFF, WriteBack.COLOR_DIFF):
811 now = datetime.utcnow()
812 src_name = f"STDIN\t{then} +0000"
813 dst_name = f"STDOUT\t{now} +0000"
814 d = diff(src, dst, src_name, dst_name)
815 if write_back == WriteBack.COLOR_DIFF:
817 f = wrap_stream_for_windows(f)
822 def format_file_contents(src_contents: str, *, fast: bool, mode: Mode) -> FileContent:
823 """Reformat contents of a file and return new contents.
825 If `fast` is False, additionally confirm that the reformatted code is
826 valid by calling :func:`assert_equivalent` and :func:`assert_stable` on it.
827 `mode` is passed to :func:`format_str`.
829 if not src_contents.strip():
832 dst_contents = format_str(src_contents, mode=mode)
833 if src_contents == dst_contents:
837 assert_equivalent(src_contents, dst_contents)
839 # Forced second pass to work around optional trailing commas (becoming
840 # forced trailing commas on pass 2) interacting differently with optional
841 # parentheses. Admittedly ugly.
842 dst_contents_pass2 = format_str(dst_contents, mode=mode)
843 if dst_contents != dst_contents_pass2:
844 dst_contents = dst_contents_pass2
845 assert_equivalent(src_contents, dst_contents, pass_num=2)
846 assert_stable(src_contents, dst_contents, mode=mode)
847 # Note: no need to explicitly call `assert_stable` if `dst_contents` was
848 # the same as `dst_contents_pass2`.
852 def format_str(src_contents: str, *, mode: Mode) -> FileContent:
853 """Reformat a string and return new contents.
855 `mode` determines formatting options, such as how many characters per line are
859 >>> print(black.format_str("def f(arg:str='')->None:...", mode=black.Mode()))
860 def f(arg: str = "") -> None:
863 A more complex example:
866 ... black.format_str(
867 ... "def f(arg:str='')->None: hey",
869 ... target_versions={black.TargetVersion.PY36},
871 ... string_normalization=False,
882 src_node = lib2to3_parse(src_contents.lstrip(), mode.target_versions)
884 future_imports = get_future_imports(src_node)
885 if mode.target_versions:
886 versions = mode.target_versions
888 versions = detect_target_versions(src_node)
889 normalize_fmt_off(src_node)
890 lines = LineGenerator(
892 remove_u_prefix="unicode_literals" in future_imports
893 or supports_feature(versions, Feature.UNICODE_LITERALS),
895 elt = EmptyLineTracker(is_pyi=mode.is_pyi)
896 empty_line = Line(mode=mode)
898 split_line_features = {
900 for feature in {Feature.TRAILING_COMMA_IN_CALL, Feature.TRAILING_COMMA_IN_DEF}
901 if supports_feature(versions, feature)
903 for current_line in lines.visit(src_node):
904 dst_contents.append(str(empty_line) * after)
905 before, after = elt.maybe_empty_lines(current_line)
906 dst_contents.append(str(empty_line) * before)
907 for line in transform_line(
908 current_line, mode=mode, features=split_line_features
910 dst_contents.append(str(line))
911 return "".join(dst_contents)
914 def decode_bytes(src: bytes) -> Tuple[FileContent, Encoding, NewLine]:
915 """Return a tuple of (decoded_contents, encoding, newline).
917 `newline` is either CRLF or LF but `decoded_contents` is decoded with
918 universal newlines (i.e. only contains LF).
920 srcbuf = io.BytesIO(src)
921 encoding, lines = tokenize.detect_encoding(srcbuf.readline)
923 return "", encoding, "\n"
925 newline = "\r\n" if b"\r\n" == lines[0][-2:] else "\n"
927 with io.TextIOWrapper(srcbuf, encoding) as tiow:
928 return tiow.read(), encoding, newline
931 def get_features_used(node: Node) -> Set[Feature]:
932 """Return a set of (relatively) new Python features used in this file.
934 Currently looking for:
936 - underscores in numeric literals;
937 - trailing commas after * or ** in function signatures and calls;
938 - positional only arguments in function signatures and lambdas;
939 - assignment expression;
940 - relaxed decorator syntax;
942 features: Set[Feature] = set()
943 for n in node.pre_order():
944 if n.type == token.STRING:
945 value_head = n.value[:2] # type: ignore
946 if value_head in {'f"', 'F"', "f'", "F'", "rf", "fr", "RF", "FR"}:
947 features.add(Feature.F_STRINGS)
949 elif n.type == token.NUMBER:
950 if "_" in n.value: # type: ignore
951 features.add(Feature.NUMERIC_UNDERSCORES)
953 elif n.type == token.SLASH:
954 if n.parent and n.parent.type in {syms.typedargslist, syms.arglist}:
955 features.add(Feature.POS_ONLY_ARGUMENTS)
957 elif n.type == token.COLONEQUAL:
958 features.add(Feature.ASSIGNMENT_EXPRESSIONS)
960 elif n.type == syms.decorator:
961 if len(n.children) > 1 and not is_simple_decorator_expression(
964 features.add(Feature.RELAXED_DECORATORS)
967 n.type in {syms.typedargslist, syms.arglist}
969 and n.children[-1].type == token.COMMA
971 if n.type == syms.typedargslist:
972 feature = Feature.TRAILING_COMMA_IN_DEF
974 feature = Feature.TRAILING_COMMA_IN_CALL
976 for ch in n.children:
978 features.add(feature)
980 if ch.type == syms.argument:
981 for argch in ch.children:
982 if argch.type in STARS:
983 features.add(feature)
988 def detect_target_versions(node: Node) -> Set[TargetVersion]:
989 """Detect the version to target based on the nodes used."""
990 features = get_features_used(node)
992 version for version in TargetVersion if features <= VERSION_TO_FEATURES[version]
996 def get_future_imports(node: Node) -> Set[str]:
997 """Return a set of __future__ imports in the file."""
998 imports: Set[str] = set()
1000 def get_imports_from_children(children: List[LN]) -> Generator[str, None, None]:
1001 for child in children:
1002 if isinstance(child, Leaf):
1003 if child.type == token.NAME:
1006 elif child.type == syms.import_as_name:
1007 orig_name = child.children[0]
1008 assert isinstance(orig_name, Leaf), "Invalid syntax parsing imports"
1009 assert orig_name.type == token.NAME, "Invalid syntax parsing imports"
1010 yield orig_name.value
1012 elif child.type == syms.import_as_names:
1013 yield from get_imports_from_children(child.children)
1016 raise AssertionError("Invalid syntax parsing imports")
1018 for child in node.children:
1019 if child.type != syms.simple_stmt:
1022 first_child = child.children[0]
1023 if isinstance(first_child, Leaf):
1024 # Continue looking if we see a docstring; otherwise stop.
1026 len(child.children) == 2
1027 and first_child.type == token.STRING
1028 and child.children[1].type == token.NEWLINE
1034 elif first_child.type == syms.import_from:
1035 module_name = first_child.children[1]
1036 if not isinstance(module_name, Leaf) or module_name.value != "__future__":
1039 imports |= set(get_imports_from_children(first_child.children[3:]))
1046 def assert_equivalent(src: str, dst: str, *, pass_num: int = 1) -> None:
1047 """Raise AssertionError if `src` and `dst` aren't equivalent."""
1049 src_ast = parse_ast(src)
1050 except Exception as exc:
1051 raise AssertionError(
1052 "cannot use --safe with this file; failed to parse source file. AST"
1053 f" error message: {exc}"
1057 dst_ast = parse_ast(dst)
1058 except Exception as exc:
1059 log = dump_to_file("".join(traceback.format_tb(exc.__traceback__)), dst)
1060 raise AssertionError(
1061 f"INTERNAL ERROR: Black produced invalid code on pass {pass_num}: {exc}. "
1062 "Please report a bug on https://github.com/psf/black/issues. "
1063 f"This invalid output might be helpful: {log}"
1066 src_ast_str = "\n".join(stringify_ast(src_ast))
1067 dst_ast_str = "\n".join(stringify_ast(dst_ast))
1068 if src_ast_str != dst_ast_str:
1069 log = dump_to_file(diff(src_ast_str, dst_ast_str, "src", "dst"))
1070 raise AssertionError(
1071 "INTERNAL ERROR: Black produced code that is not equivalent to the"
1072 f" source on pass {pass_num}. Please report a bug on "
1073 f"https://github.com/psf/black/issues. This diff might be helpful: {log}"
1077 def assert_stable(src: str, dst: str, mode: Mode) -> None:
1078 """Raise AssertionError if `dst` reformats differently the second time."""
1079 newdst = format_str(dst, mode=mode)
1083 diff(src, dst, "source", "first pass"),
1084 diff(dst, newdst, "first pass", "second pass"),
1086 raise AssertionError(
1087 "INTERNAL ERROR: Black produced different code on the second pass of the"
1088 " formatter. Please report a bug on https://github.com/psf/black/issues."
1089 f" This diff might be helpful: {log}"
1094 def nullcontext() -> Iterator[None]:
1095 """Return an empty context manager.
1097 To be used like `nullcontext` in Python 3.7.
1102 def patch_click() -> None:
1103 """Make Click not crash on Python 3.6 with LANG=C.
1105 On certain misconfigured environments, Python 3 selects the ASCII encoding as the
1106 default which restricts paths that it can access during the lifetime of the
1107 application. Click refuses to work in this scenario by raising a RuntimeError.
1109 In case of Black the likelihood that non-ASCII characters are going to be used in
1110 file paths is minimal since it's Python source code. Moreover, this crash was
1111 spurious on Python 3.7 thanks to PEP 538 and PEP 540.
1114 from click import core
1115 from click import _unicodefun # type: ignore
1116 except ModuleNotFoundError:
1119 for module in (core, _unicodefun):
1120 if hasattr(module, "_verify_python3_env"):
1121 module._verify_python3_env = lambda: None # type: ignore
1122 if hasattr(module, "_verify_python_env"):
1123 module._verify_python_env = lambda: None # type: ignore
1126 def patched_main() -> None:
1127 maybe_install_uvloop()
1133 if __name__ == "__main__":