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]",
247 default=DEFAULT_INCLUDES,
248 callback=validate_regex,
250 "A regular expression that matches files and directories that should be"
251 " included on recursive searches. An empty value means all files are included"
252 " regardless of the name. Use forward slashes for directories on all platforms"
253 " (Windows, too). Exclusions are calculated first, inclusions later."
260 callback=validate_regex,
262 "A regular expression that matches files and directories that should be"
263 " excluded on recursive searches. An empty value means no paths are excluded."
264 " Use forward slashes for directories on all platforms (Windows, too)."
265 " Exclusions are calculated first, inclusions later. [default:"
266 f" {DEFAULT_EXCLUDES}]"
273 callback=validate_regex,
275 "Like --exclude, but adds additional files and directories on top of the"
276 " excluded ones. (Useful if you simply want to add to the default)"
282 callback=validate_regex,
284 "Like --exclude, but files and directories matching this regex will be "
285 "excluded even when they are passed explicitly as arguments."
292 "The name of the file when passing it through stdin. Useful to make "
293 "sure Black will respect --force-exclude option on some "
294 "editors that rely on using stdin."
302 "Don't emit non-error messages to stderr. Errors are still emitted; silence"
303 " those with 2>/dev/null."
311 "Also emit messages to stderr about files that were not changed or were ignored"
312 " due to exclusion patterns."
315 @click.version_option(version=__version__)
320 exists=True, file_okay=True, dir_okay=True, readable=True, allow_dash=True
335 callback=read_pyproject_toml,
336 help="Read configuration from FILE path.",
343 target_version: List[TargetVersion],
349 skip_string_normalization: bool,
350 skip_magic_trailing_comma: bool,
351 experimental_string_processing: bool,
355 exclude: Optional[Pattern],
356 extend_exclude: Optional[Pattern],
357 force_exclude: Optional[Pattern],
358 stdin_filename: Optional[str],
359 src: Tuple[str, ...],
360 config: Optional[str],
362 """The uncompromising code formatter."""
363 write_back = WriteBack.from_configuration(check=check, diff=diff, color=color)
365 versions = set(target_version)
367 # We'll autodetect later.
370 target_versions=versions,
371 line_length=line_length,
373 string_normalization=not skip_string_normalization,
374 magic_trailing_comma=not skip_magic_trailing_comma,
375 experimental_string_processing=experimental_string_processing,
377 if config and verbose:
378 out(f"Using configuration from {config}.", bold=False, fg="blue")
381 # Run in quiet mode by default with -c; the extra output isn't useful.
382 # You can still pass -v to get verbose output.
385 report = Report(check=check, diff=diff, quiet=quiet, verbose=verbose)
389 content=code, fast=fast, write_back=write_back, mode=mode, report=report
392 sources = get_sources(
399 extend_exclude=extend_exclude,
400 force_exclude=force_exclude,
402 stdin_filename=stdin_filename,
407 "No Python files are present to be formatted. Nothing to do 😴",
413 if len(sources) == 1:
417 write_back=write_back,
425 write_back=write_back,
430 if verbose or not quiet:
431 out("Oh no! 💥 💔 💥" if report.return_code else "All done! ✨ 🍰 ✨")
433 click.secho(str(report), err=True)
434 ctx.exit(report.return_code)
440 src: Tuple[str, ...],
443 include: Pattern[str],
444 exclude: Optional[Pattern[str]],
445 extend_exclude: Optional[Pattern[str]],
446 force_exclude: Optional[Pattern[str]],
448 stdin_filename: Optional[str],
450 """Compute the set of files to be formatted."""
452 root = find_project_root(src)
453 sources: Set[Path] = set()
454 path_empty(src, "No Path provided. Nothing to do 😴", quiet, verbose, ctx)
457 exclude = re_compile_maybe_verbose(DEFAULT_EXCLUDES)
458 gitignore = get_gitignore(root)
463 if s == "-" and stdin_filename:
464 p = Path(stdin_filename)
470 if is_stdin or p.is_file():
471 normalized_path = normalize_path_maybe_ignore(p, root, report)
472 if normalized_path is None:
475 normalized_path = "/" + normalized_path
476 # Hard-exclude any files that matches the `--force-exclude` regex.
478 force_exclude_match = force_exclude.search(normalized_path)
480 force_exclude_match = None
481 if force_exclude_match and force_exclude_match.group(0):
482 report.path_ignored(p, "matches the --force-exclude regular expression")
486 p = Path(f"{STDIN_PLACEHOLDER}{str(p)}")
505 err(f"invalid path: {s}")
510 src: Sized, msg: str, quiet: bool, verbose: bool, ctx: click.Context
513 Exit if there is no `src` provided for formatting
516 if verbose or not quiet:
522 content: str, fast: bool, write_back: WriteBack, mode: Mode, report: Report
525 Reformat and print out `content` without spawning child processes.
526 Similar to `reformat_one`, but for string content.
528 `fast`, `write_back`, and `mode` options are passed to
529 :func:`format_file_in_place` or :func:`format_stdin_to_stdout`.
531 path = Path("<string>")
534 if format_stdin_to_stdout(
535 content=content, fast=fast, write_back=write_back, mode=mode
537 changed = Changed.YES
538 report.done(path, changed)
539 except Exception as exc:
541 traceback.print_exc()
542 report.failed(path, str(exc))
546 src: Path, fast: bool, write_back: WriteBack, mode: Mode, report: "Report"
548 """Reformat a single file under `src` without spawning child processes.
550 `fast`, `write_back`, and `mode` options are passed to
551 :func:`format_file_in_place` or :func:`format_stdin_to_stdout`.
558 elif str(src).startswith(STDIN_PLACEHOLDER):
560 # Use the original name again in case we want to print something
562 src = Path(str(src)[len(STDIN_PLACEHOLDER) :])
567 if src.suffix == ".pyi":
568 mode = replace(mode, is_pyi=True)
569 if format_stdin_to_stdout(fast=fast, write_back=write_back, mode=mode):
570 changed = Changed.YES
573 if write_back not in (WriteBack.DIFF, WriteBack.COLOR_DIFF):
574 cache = read_cache(mode)
575 res_src = src.resolve()
576 res_src_s = str(res_src)
577 if res_src_s in cache and cache[res_src_s] == get_cache_info(res_src):
578 changed = Changed.CACHED
579 if changed is not Changed.CACHED and format_file_in_place(
580 src, fast=fast, write_back=write_back, mode=mode
582 changed = Changed.YES
583 if (write_back is WriteBack.YES and changed is not Changed.CACHED) or (
584 write_back is WriteBack.CHECK and changed is Changed.NO
586 write_cache(cache, [src], mode)
587 report.done(src, changed)
588 except Exception as exc:
590 traceback.print_exc()
591 report.failed(src, str(exc))
595 sources: Set[Path], fast: bool, write_back: WriteBack, mode: Mode, report: "Report"
597 """Reformat multiple files using a ProcessPoolExecutor."""
599 loop = asyncio.get_event_loop()
600 worker_count = os.cpu_count()
601 if sys.platform == "win32":
602 # Work around https://bugs.python.org/issue26903
603 worker_count = min(worker_count, 60)
605 executor = ProcessPoolExecutor(max_workers=worker_count)
606 except (ImportError, OSError):
607 # we arrive here if the underlying system does not support multi-processing
608 # like in AWS Lambda or Termux, in which case we gracefully fallback to
609 # a ThreadPoolExecutor with just a single worker (more workers would not do us
610 # any good due to the Global Interpreter Lock)
611 executor = ThreadPoolExecutor(max_workers=1)
614 loop.run_until_complete(
618 write_back=write_back,
627 if executor is not None:
631 async def schedule_formatting(
634 write_back: WriteBack,
637 loop: asyncio.AbstractEventLoop,
640 """Run formatting of `sources` in parallel using the provided `executor`.
642 (Use ProcessPoolExecutors for actual parallelism.)
644 `write_back`, `fast`, and `mode` options are passed to
645 :func:`format_file_in_place`.
648 if write_back not in (WriteBack.DIFF, WriteBack.COLOR_DIFF):
649 cache = read_cache(mode)
650 sources, cached = filter_cached(cache, sources)
651 for src in sorted(cached):
652 report.done(src, Changed.CACHED)
657 sources_to_cache = []
659 if write_back in (WriteBack.DIFF, WriteBack.COLOR_DIFF):
660 # For diff output, we need locks to ensure we don't interleave output
661 # from different processes.
663 lock = manager.Lock()
665 asyncio.ensure_future(
666 loop.run_in_executor(
667 executor, format_file_in_place, src, fast, mode, write_back, lock
670 for src in sorted(sources)
672 pending = tasks.keys()
674 loop.add_signal_handler(signal.SIGINT, cancel, pending)
675 loop.add_signal_handler(signal.SIGTERM, cancel, pending)
676 except NotImplementedError:
677 # There are no good alternatives for these on Windows.
680 done, _ = await asyncio.wait(pending, return_when=asyncio.FIRST_COMPLETED)
682 src = tasks.pop(task)
684 cancelled.append(task)
685 elif task.exception():
686 report.failed(src, str(task.exception()))
688 changed = Changed.YES if task.result() else Changed.NO
689 # If the file was written back or was successfully checked as
690 # well-formatted, store this information in the cache.
691 if write_back is WriteBack.YES or (
692 write_back is WriteBack.CHECK and changed is Changed.NO
694 sources_to_cache.append(src)
695 report.done(src, changed)
697 await asyncio.gather(*cancelled, loop=loop, return_exceptions=True)
699 write_cache(cache, sources_to_cache, mode)
702 def format_file_in_place(
706 write_back: WriteBack = WriteBack.NO,
707 lock: Any = None, # multiprocessing.Manager().Lock() is some crazy proxy
709 """Format file under `src` path. Return True if changed.
711 If `write_back` is DIFF, write a diff to stdout. If it is YES, write reformatted
713 `mode` and `fast` options are passed to :func:`format_file_contents`.
715 if src.suffix == ".pyi":
716 mode = replace(mode, is_pyi=True)
718 then = datetime.utcfromtimestamp(src.stat().st_mtime)
719 with open(src, "rb") as buf:
720 src_contents, encoding, newline = decode_bytes(buf.read())
722 dst_contents = format_file_contents(src_contents, fast=fast, mode=mode)
723 except NothingChanged:
726 if write_back == WriteBack.YES:
727 with open(src, "w", encoding=encoding, newline=newline) as f:
728 f.write(dst_contents)
729 elif write_back in (WriteBack.DIFF, WriteBack.COLOR_DIFF):
730 now = datetime.utcnow()
731 src_name = f"{src}\t{then} +0000"
732 dst_name = f"{src}\t{now} +0000"
733 diff_contents = diff(src_contents, dst_contents, src_name, dst_name)
735 if write_back == WriteBack.COLOR_DIFF:
736 diff_contents = color_diff(diff_contents)
738 with lock or nullcontext():
739 f = io.TextIOWrapper(
745 f = wrap_stream_for_windows(f)
746 f.write(diff_contents)
752 def format_stdin_to_stdout(
755 content: Optional[str] = None,
756 write_back: WriteBack = WriteBack.NO,
759 """Format file on stdin. Return True if changed.
761 If content is None, it's read from sys.stdin.
763 If `write_back` is YES, write reformatted code back to stdout. If it is DIFF,
764 write a diff to stdout. The `mode` argument is passed to
765 :func:`format_file_contents`.
767 then = datetime.utcnow()
770 src, encoding, newline = decode_bytes(sys.stdin.buffer.read())
772 src, encoding, newline = content, "utf-8", ""
776 dst = format_file_contents(src, fast=fast, mode=mode)
779 except NothingChanged:
783 f = io.TextIOWrapper(
784 sys.stdout.buffer, encoding=encoding, newline=newline, write_through=True
786 if write_back == WriteBack.YES:
787 # Make sure there's a newline after the content
788 dst += "" if dst[-1] == "\n" else "\n"
790 elif write_back in (WriteBack.DIFF, WriteBack.COLOR_DIFF):
791 now = datetime.utcnow()
792 src_name = f"STDIN\t{then} +0000"
793 dst_name = f"STDOUT\t{now} +0000"
794 d = diff(src, dst, src_name, dst_name)
795 if write_back == WriteBack.COLOR_DIFF:
797 f = wrap_stream_for_windows(f)
802 def format_file_contents(src_contents: str, *, fast: bool, mode: Mode) -> FileContent:
803 """Reformat contents of a file and return new contents.
805 If `fast` is False, additionally confirm that the reformatted code is
806 valid by calling :func:`assert_equivalent` and :func:`assert_stable` on it.
807 `mode` is passed to :func:`format_str`.
809 if not src_contents.strip():
812 dst_contents = format_str(src_contents, mode=mode)
813 if src_contents == dst_contents:
817 assert_equivalent(src_contents, dst_contents)
819 # Forced second pass to work around optional trailing commas (becoming
820 # forced trailing commas on pass 2) interacting differently with optional
821 # parentheses. Admittedly ugly.
822 dst_contents_pass2 = format_str(dst_contents, mode=mode)
823 if dst_contents != dst_contents_pass2:
824 dst_contents = dst_contents_pass2
825 assert_equivalent(src_contents, dst_contents, pass_num=2)
826 assert_stable(src_contents, dst_contents, mode=mode)
827 # Note: no need to explicitly call `assert_stable` if `dst_contents` was
828 # the same as `dst_contents_pass2`.
832 def format_str(src_contents: str, *, mode: Mode) -> FileContent:
833 """Reformat a string and return new contents.
835 `mode` determines formatting options, such as how many characters per line are
839 >>> print(black.format_str("def f(arg:str='')->None:...", mode=black.Mode()))
840 def f(arg: str = "") -> None:
843 A more complex example:
846 ... black.format_str(
847 ... "def f(arg:str='')->None: hey",
849 ... target_versions={black.TargetVersion.PY36},
851 ... string_normalization=False,
862 src_node = lib2to3_parse(src_contents.lstrip(), mode.target_versions)
864 future_imports = get_future_imports(src_node)
865 if mode.target_versions:
866 versions = mode.target_versions
868 versions = detect_target_versions(src_node)
869 normalize_fmt_off(src_node)
870 lines = LineGenerator(
872 remove_u_prefix="unicode_literals" in future_imports
873 or supports_feature(versions, Feature.UNICODE_LITERALS),
875 elt = EmptyLineTracker(is_pyi=mode.is_pyi)
876 empty_line = Line(mode=mode)
878 split_line_features = {
880 for feature in {Feature.TRAILING_COMMA_IN_CALL, Feature.TRAILING_COMMA_IN_DEF}
881 if supports_feature(versions, feature)
883 for current_line in lines.visit(src_node):
884 dst_contents.append(str(empty_line) * after)
885 before, after = elt.maybe_empty_lines(current_line)
886 dst_contents.append(str(empty_line) * before)
887 for line in transform_line(
888 current_line, mode=mode, features=split_line_features
890 dst_contents.append(str(line))
891 return "".join(dst_contents)
894 def decode_bytes(src: bytes) -> Tuple[FileContent, Encoding, NewLine]:
895 """Return a tuple of (decoded_contents, encoding, newline).
897 `newline` is either CRLF or LF but `decoded_contents` is decoded with
898 universal newlines (i.e. only contains LF).
900 srcbuf = io.BytesIO(src)
901 encoding, lines = tokenize.detect_encoding(srcbuf.readline)
903 return "", encoding, "\n"
905 newline = "\r\n" if b"\r\n" == lines[0][-2:] else "\n"
907 with io.TextIOWrapper(srcbuf, encoding) as tiow:
908 return tiow.read(), encoding, newline
911 def get_features_used(node: Node) -> Set[Feature]:
912 """Return a set of (relatively) new Python features used in this file.
914 Currently looking for:
916 - underscores in numeric literals;
917 - trailing commas after * or ** in function signatures and calls;
918 - positional only arguments in function signatures and lambdas;
919 - assignment expression;
920 - relaxed decorator syntax;
922 features: Set[Feature] = set()
923 for n in node.pre_order():
924 if n.type == token.STRING:
925 value_head = n.value[:2] # type: ignore
926 if value_head in {'f"', 'F"', "f'", "F'", "rf", "fr", "RF", "FR"}:
927 features.add(Feature.F_STRINGS)
929 elif n.type == token.NUMBER:
930 if "_" in n.value: # type: ignore
931 features.add(Feature.NUMERIC_UNDERSCORES)
933 elif n.type == token.SLASH:
934 if n.parent and n.parent.type in {syms.typedargslist, syms.arglist}:
935 features.add(Feature.POS_ONLY_ARGUMENTS)
937 elif n.type == token.COLONEQUAL:
938 features.add(Feature.ASSIGNMENT_EXPRESSIONS)
940 elif n.type == syms.decorator:
941 if len(n.children) > 1 and not is_simple_decorator_expression(
944 features.add(Feature.RELAXED_DECORATORS)
947 n.type in {syms.typedargslist, syms.arglist}
949 and n.children[-1].type == token.COMMA
951 if n.type == syms.typedargslist:
952 feature = Feature.TRAILING_COMMA_IN_DEF
954 feature = Feature.TRAILING_COMMA_IN_CALL
956 for ch in n.children:
958 features.add(feature)
960 if ch.type == syms.argument:
961 for argch in ch.children:
962 if argch.type in STARS:
963 features.add(feature)
968 def detect_target_versions(node: Node) -> Set[TargetVersion]:
969 """Detect the version to target based on the nodes used."""
970 features = get_features_used(node)
972 version for version in TargetVersion if features <= VERSION_TO_FEATURES[version]
976 def get_future_imports(node: Node) -> Set[str]:
977 """Return a set of __future__ imports in the file."""
978 imports: Set[str] = set()
980 def get_imports_from_children(children: List[LN]) -> Generator[str, None, None]:
981 for child in children:
982 if isinstance(child, Leaf):
983 if child.type == token.NAME:
986 elif child.type == syms.import_as_name:
987 orig_name = child.children[0]
988 assert isinstance(orig_name, Leaf), "Invalid syntax parsing imports"
989 assert orig_name.type == token.NAME, "Invalid syntax parsing imports"
990 yield orig_name.value
992 elif child.type == syms.import_as_names:
993 yield from get_imports_from_children(child.children)
996 raise AssertionError("Invalid syntax parsing imports")
998 for child in node.children:
999 if child.type != syms.simple_stmt:
1002 first_child = child.children[0]
1003 if isinstance(first_child, Leaf):
1004 # Continue looking if we see a docstring; otherwise stop.
1006 len(child.children) == 2
1007 and first_child.type == token.STRING
1008 and child.children[1].type == token.NEWLINE
1014 elif first_child.type == syms.import_from:
1015 module_name = first_child.children[1]
1016 if not isinstance(module_name, Leaf) or module_name.value != "__future__":
1019 imports |= set(get_imports_from_children(first_child.children[3:]))
1026 def assert_equivalent(src: str, dst: str, *, pass_num: int = 1) -> None:
1027 """Raise AssertionError if `src` and `dst` aren't equivalent."""
1029 src_ast = parse_ast(src)
1030 except Exception as exc:
1031 raise AssertionError(
1032 "cannot use --safe with this file; failed to parse source file. AST"
1033 f" error message: {exc}"
1037 dst_ast = parse_ast(dst)
1038 except Exception as exc:
1039 log = dump_to_file("".join(traceback.format_tb(exc.__traceback__)), dst)
1040 raise AssertionError(
1041 f"INTERNAL ERROR: Black produced invalid code on pass {pass_num}: {exc}. "
1042 "Please report a bug on https://github.com/psf/black/issues. "
1043 f"This invalid output might be helpful: {log}"
1046 src_ast_str = "\n".join(stringify_ast(src_ast))
1047 dst_ast_str = "\n".join(stringify_ast(dst_ast))
1048 if src_ast_str != dst_ast_str:
1049 log = dump_to_file(diff(src_ast_str, dst_ast_str, "src", "dst"))
1050 raise AssertionError(
1051 "INTERNAL ERROR: Black produced code that is not equivalent to the"
1052 f" source on pass {pass_num}. Please report a bug on "
1053 f"https://github.com/psf/black/issues. This diff might be helpful: {log}"
1057 def assert_stable(src: str, dst: str, mode: Mode) -> None:
1058 """Raise AssertionError if `dst` reformats differently the second time."""
1059 newdst = format_str(dst, mode=mode)
1063 diff(src, dst, "source", "first pass"),
1064 diff(dst, newdst, "first pass", "second pass"),
1066 raise AssertionError(
1067 "INTERNAL ERROR: Black produced different code on the second pass of the"
1068 " formatter. Please report a bug on https://github.com/psf/black/issues."
1069 f" This diff might be helpful: {log}"
1074 def nullcontext() -> Iterator[None]:
1075 """Return an empty context manager.
1077 To be used like `nullcontext` in Python 3.7.
1082 def patch_click() -> None:
1083 """Make Click not crash on Python 3.6 with LANG=C.
1085 On certain misconfigured environments, Python 3 selects the ASCII encoding as the
1086 default which restricts paths that it can access during the lifetime of the
1087 application. Click refuses to work in this scenario by raising a RuntimeError.
1089 In case of Black the likelihood that non-ASCII characters are going to be used in
1090 file paths is minimal since it's Python source code. Moreover, this crash was
1091 spurious on Python 3.7 thanks to PEP 538 and PEP 540.
1094 from click import core
1095 from click import _unicodefun # type: ignore
1096 except ModuleNotFoundError:
1099 for module in (core, _unicodefun):
1100 if hasattr(module, "_verify_python3_env"):
1101 module._verify_python3_env = lambda: None # type: ignore
1102 if hasattr(module, "_verify_python_env"):
1103 module._verify_python_env = lambda: None # type: ignore
1106 def patched_main() -> None:
1107 maybe_install_uvloop()
1113 if __name__ == "__main__":