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 json.decoder import JSONDecodeError
4 from concurrent.futures import Executor, ThreadPoolExecutor, ProcessPoolExecutor
5 from contextlib import contextmanager
6 from datetime import datetime
9 from multiprocessing import Manager, freeze_support
11 from pathlib import Path
12 from pathspec.patterns.gitwildmatch import GitWildMatchPatternError
34 from dataclasses import replace
35 from mypy_extensions import mypyc_attr
37 from black.const import DEFAULT_LINE_LENGTH, DEFAULT_INCLUDES, DEFAULT_EXCLUDES
38 from black.const import STDIN_PLACEHOLDER
39 from black.nodes import STARS, syms, is_simple_decorator_expression
40 from black.nodes import is_string_token
41 from black.lines import Line, EmptyLineTracker
42 from black.linegen import transform_line, LineGenerator, LN
43 from black.comments import normalize_fmt_off
44 from black.mode import FUTURE_FLAG_TO_FEATURE, Mode, TargetVersion
45 from black.mode import Feature, supports_feature, VERSION_TO_FEATURES
46 from black.cache import read_cache, write_cache, get_cache_info, filter_cached, Cache
47 from black.concurrency import cancel, shutdown, maybe_install_uvloop
48 from black.output import dump_to_file, ipynb_diff, diff, color_diff, out, err
49 from black.report import Report, Changed, NothingChanged
50 from black.files import find_project_root, find_pyproject_toml, parse_pyproject_toml
51 from black.files import gen_python_files, get_gitignore, normalize_path_maybe_ignore
52 from black.files import wrap_stream_for_windows
53 from black.parsing import InvalidInput # noqa F401
54 from black.parsing import lib2to3_parse, parse_ast, stringify_ast
55 from black.handle_ipynb_magics import (
58 remove_trailing_semicolon,
59 put_trailing_semicolon_back,
62 jupyter_dependencies_are_installed,
67 from blib2to3.pytree import Node, Leaf
68 from blib2to3.pgen2 import token
70 from _black_version import version as __version__
72 COMPILED = Path(__file__).suffix in (".pyd", ".so")
80 class WriteBack(Enum):
88 def from_configuration(
89 cls, *, check: bool, diff: bool, color: bool = False
91 if check and not diff:
97 return cls.DIFF if diff else cls.YES
100 # Legacy name, left for integrations.
103 DEFAULT_WORKERS = os.cpu_count()
106 def read_pyproject_toml(
107 ctx: click.Context, param: click.Parameter, value: Optional[str]
109 """Inject Black configuration from "pyproject.toml" into defaults in `ctx`.
111 Returns the path to a successfully found and read configuration file, None
115 value = find_pyproject_toml(ctx.params.get("src", ()))
120 config = parse_pyproject_toml(value)
121 except (OSError, ValueError) as e:
122 raise click.FileError(
123 filename=value, hint=f"Error reading configuration file: {e}"
129 # Sanitize the values to be Click friendly. For more information please see:
130 # https://github.com/psf/black/issues/1458
131 # https://github.com/pallets/click/issues/1567
133 k: str(v) if not isinstance(v, (list, dict)) else v
134 for k, v in config.items()
137 target_version = config.get("target_version")
138 if target_version is not None and not isinstance(target_version, list):
139 raise click.BadOptionUsage(
140 "target-version", "Config key target-version must be a list"
143 default_map: Dict[str, Any] = {}
145 default_map.update(ctx.default_map)
146 default_map.update(config)
148 ctx.default_map = default_map
152 def target_version_option_callback(
153 c: click.Context, p: Union[click.Option, click.Parameter], v: Tuple[str, ...]
154 ) -> List[TargetVersion]:
155 """Compute the target versions from a --target-version flag.
157 This is its own function because mypy couldn't infer the type correctly
158 when it was a lambda, causing mypyc trouble.
160 return [TargetVersion[val.upper()] for val in v]
163 def re_compile_maybe_verbose(regex: str) -> Pattern[str]:
164 """Compile a regular expression string in `regex`.
166 If it contains newlines, use verbose mode.
169 regex = "(?x)" + regex
170 compiled: Pattern[str] = re.compile(regex)
176 param: click.Parameter,
177 value: Optional[str],
178 ) -> Optional[Pattern[str]]:
180 return re_compile_maybe_verbose(value) if value is not None else None
181 except re.error as e:
182 raise click.BadParameter(f"Not a valid regular expression: {e}") from None
186 context_settings={"help_option_names": ["-h", "--help"]},
187 # While Click does set this field automatically using the docstring, mypyc
188 # (annoyingly) strips 'em so we need to set it here too.
189 help="The uncompromising code formatter.",
191 @click.option("-c", "--code", type=str, help="Format the code passed in as a string.")
196 default=DEFAULT_LINE_LENGTH,
197 help="How many characters per line to allow.",
203 type=click.Choice([v.name.lower() for v in TargetVersion]),
204 callback=target_version_option_callback,
207 "Python versions that should be supported by Black's output. [default: per-file"
215 "Format all input files like typing stubs regardless of file extension (useful"
216 " when piping source on standard input)."
223 "Format all input files like Jupyter Notebooks regardless of file extension "
224 "(useful when piping source on standard input)."
229 "--skip-string-normalization",
231 help="Don't normalize string quotes or prefixes.",
235 "--skip-magic-trailing-comma",
237 help="Don't use trailing commas as a reason to split lines.",
240 "--experimental-string-processing",
244 "Experimental option that performs more normalization on string literals."
245 " Currently disabled because it leads to some crashes."
252 "Don't write the files back, just return the status. Return code 0 means"
253 " nothing would change. Return code 1 means some files would be reformatted."
254 " Return code 123 means there was an internal error."
260 help="Don't write the files back, just output a diff for each file on stdout.",
263 "--color/--no-color",
265 help="Show colored diff. Only applies when `--diff` is given.",
270 help="If --fast given, skip temporary sanity checks. [default: --safe]",
273 "--required-version",
276 "Require a specific version of Black to be running (useful for unifying results"
277 " across many environments e.g. with a pyproject.toml file)."
283 default=DEFAULT_INCLUDES,
284 callback=validate_regex,
286 "A regular expression that matches files and directories that should be"
287 " included on recursive searches. An empty value means all files are included"
288 " regardless of the name. Use forward slashes for directories on all platforms"
289 " (Windows, too). Exclusions are calculated first, inclusions later."
296 callback=validate_regex,
298 "A regular expression that matches files and directories that should be"
299 " excluded on recursive searches. An empty value means no paths are excluded."
300 " Use forward slashes for directories on all platforms (Windows, too)."
301 " Exclusions are calculated first, inclusions later. [default:"
302 f" {DEFAULT_EXCLUDES}]"
309 callback=validate_regex,
311 "Like --exclude, but adds additional files and directories on top of the"
312 " excluded ones. (Useful if you simply want to add to the default)"
318 callback=validate_regex,
320 "Like --exclude, but files and directories matching this regex will be "
321 "excluded even when they are passed explicitly as arguments."
328 "The name of the file when passing it through stdin. Useful to make "
329 "sure Black will respect --force-exclude option on some "
330 "editors that rely on using stdin."
336 type=click.IntRange(min=1),
337 default=DEFAULT_WORKERS,
339 help="Number of parallel workers",
346 "Don't emit non-error messages to stderr. Errors are still emitted; silence"
347 " those with 2>/dev/null."
355 "Also emit messages to stderr about files that were not changed or were ignored"
356 " due to exclusion patterns."
359 @click.version_option(
361 message=f"%(prog)s, %(version)s (compiled: {'yes' if COMPILED else 'no'})",
367 exists=True, file_okay=True, dir_okay=True, readable=True, allow_dash=True
383 callback=read_pyproject_toml,
384 help="Read configuration from FILE path.",
391 target_version: List[TargetVersion],
398 skip_string_normalization: bool,
399 skip_magic_trailing_comma: bool,
400 experimental_string_processing: bool,
403 required_version: Optional[str],
404 include: Pattern[str],
405 exclude: Optional[Pattern[str]],
406 extend_exclude: Optional[Pattern[str]],
407 force_exclude: Optional[Pattern[str]],
408 stdin_filename: Optional[str],
410 src: Tuple[str, ...],
411 config: Optional[str],
413 """The uncompromising code formatter."""
414 if config and verbose:
415 out(f"Using configuration from {config}.", bold=False, fg="blue")
417 error_msg = "Oh no! 💥 💔 💥"
418 if required_version and required_version != __version__:
420 f"{error_msg} The required version `{required_version}` does not match"
421 f" the running version `{__version__}`!"
425 err("Cannot pass both `pyi` and `ipynb` flags!")
428 write_back = WriteBack.from_configuration(check=check, diff=diff, color=color)
430 versions = set(target_version)
432 # We'll autodetect later.
435 target_versions=versions,
436 line_length=line_length,
439 string_normalization=not skip_string_normalization,
440 magic_trailing_comma=not skip_magic_trailing_comma,
441 experimental_string_processing=experimental_string_processing,
445 # Run in quiet mode by default with -c; the extra output isn't useful.
446 # You can still pass -v to get verbose output.
449 report = Report(check=check, diff=diff, quiet=quiet, verbose=verbose)
453 content=code, fast=fast, write_back=write_back, mode=mode, report=report
457 sources = get_sources(
464 extend_exclude=extend_exclude,
465 force_exclude=force_exclude,
467 stdin_filename=stdin_filename,
469 except GitWildMatchPatternError:
474 "No Python files are present to be formatted. Nothing to do 😴",
480 if len(sources) == 1:
484 write_back=write_back,
492 write_back=write_back,
498 if verbose or not quiet:
499 out(error_msg if report.return_code else "All done! ✨ 🍰 ✨")
501 click.echo(str(report), err=True)
502 ctx.exit(report.return_code)
508 src: Tuple[str, ...],
511 include: Pattern[str],
512 exclude: Optional[Pattern[str]],
513 extend_exclude: Optional[Pattern[str]],
514 force_exclude: Optional[Pattern[str]],
516 stdin_filename: Optional[str],
518 """Compute the set of files to be formatted."""
520 root = find_project_root(src)
521 sources: Set[Path] = set()
522 path_empty(src, "No Path provided. Nothing to do 😴", quiet, verbose, ctx)
525 exclude = re_compile_maybe_verbose(DEFAULT_EXCLUDES)
526 gitignore = get_gitignore(root)
531 if s == "-" and stdin_filename:
532 p = Path(stdin_filename)
538 if is_stdin or p.is_file():
539 normalized_path = normalize_path_maybe_ignore(p, root, report)
540 if normalized_path is None:
543 normalized_path = "/" + normalized_path
544 # Hard-exclude any files that matches the `--force-exclude` regex.
546 force_exclude_match = force_exclude.search(normalized_path)
548 force_exclude_match = None
549 if force_exclude_match and force_exclude_match.group(0):
550 report.path_ignored(p, "matches the --force-exclude regular expression")
554 p = Path(f"{STDIN_PLACEHOLDER}{str(p)}")
556 if p.suffix == ".ipynb" and not jupyter_dependencies_are_installed(
557 verbose=verbose, quiet=quiet
580 err(f"invalid path: {s}")
585 src: Sized, msg: str, quiet: bool, verbose: bool, ctx: click.Context
588 Exit if there is no `src` provided for formatting
591 if verbose or not quiet:
597 content: str, fast: bool, write_back: WriteBack, mode: Mode, report: Report
600 Reformat and print out `content` without spawning child processes.
601 Similar to `reformat_one`, but for string content.
603 `fast`, `write_back`, and `mode` options are passed to
604 :func:`format_file_in_place` or :func:`format_stdin_to_stdout`.
606 path = Path("<string>")
609 if format_stdin_to_stdout(
610 content=content, fast=fast, write_back=write_back, mode=mode
612 changed = Changed.YES
613 report.done(path, changed)
614 except Exception as exc:
616 traceback.print_exc()
617 report.failed(path, str(exc))
621 src: Path, fast: bool, write_back: WriteBack, mode: Mode, report: "Report"
623 """Reformat a single file under `src` without spawning child processes.
625 `fast`, `write_back`, and `mode` options are passed to
626 :func:`format_file_in_place` or :func:`format_stdin_to_stdout`.
633 elif str(src).startswith(STDIN_PLACEHOLDER):
635 # Use the original name again in case we want to print something
637 src = Path(str(src)[len(STDIN_PLACEHOLDER) :])
642 if src.suffix == ".pyi":
643 mode = replace(mode, is_pyi=True)
644 elif src.suffix == ".ipynb":
645 mode = replace(mode, is_ipynb=True)
646 if format_stdin_to_stdout(fast=fast, write_back=write_back, mode=mode):
647 changed = Changed.YES
650 if write_back not in (WriteBack.DIFF, WriteBack.COLOR_DIFF):
651 cache = read_cache(mode)
652 res_src = src.resolve()
653 res_src_s = str(res_src)
654 if res_src_s in cache and cache[res_src_s] == get_cache_info(res_src):
655 changed = Changed.CACHED
656 if changed is not Changed.CACHED and format_file_in_place(
657 src, fast=fast, write_back=write_back, mode=mode
659 changed = Changed.YES
660 if (write_back is WriteBack.YES and changed is not Changed.CACHED) or (
661 write_back is WriteBack.CHECK and changed is Changed.NO
663 write_cache(cache, [src], mode)
664 report.done(src, changed)
665 except Exception as exc:
667 traceback.print_exc()
668 report.failed(src, str(exc))
671 # diff-shades depends on being to monkeypatch this function to operate. I know it's
672 # not ideal, but this shouldn't cause any issues ... hopefully. ~ichard26
673 @mypyc_attr(patchable=True)
677 write_back: WriteBack,
680 workers: Optional[int],
682 """Reformat multiple files using a ProcessPoolExecutor."""
684 loop = asyncio.get_event_loop()
685 worker_count = workers if workers is not None else DEFAULT_WORKERS
686 if sys.platform == "win32":
687 # Work around https://bugs.python.org/issue26903
688 assert worker_count is not None
689 worker_count = min(worker_count, 60)
691 executor = ProcessPoolExecutor(max_workers=worker_count)
692 except (ImportError, NotImplementedError, OSError):
693 # we arrive here if the underlying system does not support multi-processing
694 # like in AWS Lambda or Termux, in which case we gracefully fallback to
695 # a ThreadPoolExecutor with just a single worker (more workers would not do us
696 # any good due to the Global Interpreter Lock)
697 executor = ThreadPoolExecutor(max_workers=1)
700 loop.run_until_complete(
704 write_back=write_back,
713 if executor is not None:
717 async def schedule_formatting(
720 write_back: WriteBack,
723 loop: asyncio.AbstractEventLoop,
726 """Run formatting of `sources` in parallel using the provided `executor`.
728 (Use ProcessPoolExecutors for actual parallelism.)
730 `write_back`, `fast`, and `mode` options are passed to
731 :func:`format_file_in_place`.
734 if write_back not in (WriteBack.DIFF, WriteBack.COLOR_DIFF):
735 cache = read_cache(mode)
736 sources, cached = filter_cached(cache, sources)
737 for src in sorted(cached):
738 report.done(src, Changed.CACHED)
743 sources_to_cache = []
745 if write_back in (WriteBack.DIFF, WriteBack.COLOR_DIFF):
746 # For diff output, we need locks to ensure we don't interleave output
747 # from different processes.
749 lock = manager.Lock()
751 asyncio.ensure_future(
752 loop.run_in_executor(
753 executor, format_file_in_place, src, fast, mode, write_back, lock
756 for src in sorted(sources)
758 pending = tasks.keys()
760 loop.add_signal_handler(signal.SIGINT, cancel, pending)
761 loop.add_signal_handler(signal.SIGTERM, cancel, pending)
762 except NotImplementedError:
763 # There are no good alternatives for these on Windows.
766 done, _ = await asyncio.wait(pending, return_when=asyncio.FIRST_COMPLETED)
768 src = tasks.pop(task)
770 cancelled.append(task)
771 elif task.exception():
772 report.failed(src, str(task.exception()))
774 changed = Changed.YES if task.result() else Changed.NO
775 # If the file was written back or was successfully checked as
776 # well-formatted, store this information in the cache.
777 if write_back is WriteBack.YES or (
778 write_back is WriteBack.CHECK and changed is Changed.NO
780 sources_to_cache.append(src)
781 report.done(src, changed)
783 if sys.version_info >= (3, 7):
784 await asyncio.gather(*cancelled, return_exceptions=True)
786 await asyncio.gather(*cancelled, loop=loop, return_exceptions=True)
788 write_cache(cache, sources_to_cache, mode)
791 def format_file_in_place(
795 write_back: WriteBack = WriteBack.NO,
796 lock: Any = None, # multiprocessing.Manager().Lock() is some crazy proxy
798 """Format file under `src` path. Return True if changed.
800 If `write_back` is DIFF, write a diff to stdout. If it is YES, write reformatted
802 `mode` and `fast` options are passed to :func:`format_file_contents`.
804 if src.suffix == ".pyi":
805 mode = replace(mode, is_pyi=True)
806 elif src.suffix == ".ipynb":
807 mode = replace(mode, is_ipynb=True)
809 then = datetime.utcfromtimestamp(src.stat().st_mtime)
810 with open(src, "rb") as buf:
811 src_contents, encoding, newline = decode_bytes(buf.read())
813 dst_contents = format_file_contents(src_contents, fast=fast, mode=mode)
814 except NothingChanged:
816 except JSONDecodeError:
818 f"File '{src}' cannot be parsed as valid Jupyter notebook."
821 if write_back == WriteBack.YES:
822 with open(src, "w", encoding=encoding, newline=newline) as f:
823 f.write(dst_contents)
824 elif write_back in (WriteBack.DIFF, WriteBack.COLOR_DIFF):
825 now = datetime.utcnow()
826 src_name = f"{src}\t{then} +0000"
827 dst_name = f"{src}\t{now} +0000"
829 diff_contents = ipynb_diff(src_contents, dst_contents, src_name, dst_name)
831 diff_contents = diff(src_contents, dst_contents, src_name, dst_name)
833 if write_back == WriteBack.COLOR_DIFF:
834 diff_contents = color_diff(diff_contents)
836 with lock or nullcontext():
837 f = io.TextIOWrapper(
843 f = wrap_stream_for_windows(f)
844 f.write(diff_contents)
850 def format_stdin_to_stdout(
853 content: Optional[str] = None,
854 write_back: WriteBack = WriteBack.NO,
857 """Format file on stdin. Return True if changed.
859 If content is None, it's read from sys.stdin.
861 If `write_back` is YES, write reformatted code back to stdout. If it is DIFF,
862 write a diff to stdout. The `mode` argument is passed to
863 :func:`format_file_contents`.
865 then = datetime.utcnow()
868 src, encoding, newline = decode_bytes(sys.stdin.buffer.read())
870 src, encoding, newline = content, "utf-8", ""
874 dst = format_file_contents(src, fast=fast, mode=mode)
877 except NothingChanged:
881 f = io.TextIOWrapper(
882 sys.stdout.buffer, encoding=encoding, newline=newline, write_through=True
884 if write_back == WriteBack.YES:
885 # Make sure there's a newline after the content
886 if dst and dst[-1] != "\n":
889 elif write_back in (WriteBack.DIFF, WriteBack.COLOR_DIFF):
890 now = datetime.utcnow()
891 src_name = f"STDIN\t{then} +0000"
892 dst_name = f"STDOUT\t{now} +0000"
893 d = diff(src, dst, src_name, dst_name)
894 if write_back == WriteBack.COLOR_DIFF:
896 f = wrap_stream_for_windows(f)
901 def check_stability_and_equivalence(
902 src_contents: str, dst_contents: str, *, mode: Mode
904 """Perform stability and equivalence checks.
906 Raise AssertionError if source and destination contents are not
907 equivalent, or if a second pass of the formatter would format the
910 assert_equivalent(src_contents, dst_contents)
912 # Forced second pass to work around optional trailing commas (becoming
913 # forced trailing commas on pass 2) interacting differently with optional
914 # parentheses. Admittedly ugly.
915 dst_contents_pass2 = format_str(dst_contents, mode=mode)
916 if dst_contents != dst_contents_pass2:
917 dst_contents = dst_contents_pass2
918 assert_equivalent(src_contents, dst_contents, pass_num=2)
919 assert_stable(src_contents, dst_contents, mode=mode)
920 # Note: no need to explicitly call `assert_stable` if `dst_contents` was
921 # the same as `dst_contents_pass2`.
924 def format_file_contents(src_contents: str, *, fast: bool, mode: Mode) -> FileContent:
925 """Reformat contents of a file and return new contents.
927 If `fast` is False, additionally confirm that the reformatted code is
928 valid by calling :func:`assert_equivalent` and :func:`assert_stable` on it.
929 `mode` is passed to :func:`format_str`.
931 if not src_contents.strip():
935 dst_contents = format_ipynb_string(src_contents, fast=fast, mode=mode)
937 dst_contents = format_str(src_contents, mode=mode)
938 if src_contents == dst_contents:
941 if not fast and not mode.is_ipynb:
942 # Jupyter notebooks will already have been checked above.
943 check_stability_and_equivalence(src_contents, dst_contents, mode=mode)
947 def validate_cell(src: str) -> None:
948 """Check that cell does not already contain TransformerManager transformations,
949 or non-Python cell magics, which might cause tokenizer_rt to break because of
952 If a cell contains ``!ls``, then it'll be transformed to
953 ``get_ipython().system('ls')``. However, if the cell originally contained
954 ``get_ipython().system('ls')``, then it would get transformed in the same way:
956 >>> TransformerManager().transform_cell("get_ipython().system('ls')")
957 "get_ipython().system('ls')\n"
958 >>> TransformerManager().transform_cell("!ls")
959 "get_ipython().system('ls')\n"
961 Due to the impossibility of safely roundtripping in such situations, cells
962 containing transformed magics will be ignored.
964 if any(transformed_magic in src for transformed_magic in TRANSFORMED_MAGICS):
966 if src[:2] == "%%" and src.split()[0][2:] not in PYTHON_CELL_MAGICS:
970 def format_cell(src: str, *, fast: bool, mode: Mode) -> str:
971 """Format code in given cell of Jupyter notebook.
975 - if cell has trailing semicolon, remove it;
976 - if cell has IPython magics, mask them;
978 - reinstate IPython magics;
979 - reinstate trailing semicolon (if originally present);
980 - strip trailing newlines.
982 Cells with syntax errors will not be processed, as they
983 could potentially be automagics or multi-line magics, which
984 are currently not supported.
987 src_without_trailing_semicolon, has_trailing_semicolon = remove_trailing_semicolon(
991 masked_src, replacements = mask_cell(src_without_trailing_semicolon)
993 raise NothingChanged from None
994 masked_dst = format_str(masked_src, mode=mode)
996 check_stability_and_equivalence(masked_src, masked_dst, mode=mode)
997 dst_without_trailing_semicolon = unmask_cell(masked_dst, replacements)
998 dst = put_trailing_semicolon_back(
999 dst_without_trailing_semicolon, has_trailing_semicolon
1001 dst = dst.rstrip("\n")
1003 raise NothingChanged from None
1007 def validate_metadata(nb: MutableMapping[str, Any]) -> None:
1008 """If notebook is marked as non-Python, don't format it.
1010 All notebook metadata fields are optional, see
1011 https://nbformat.readthedocs.io/en/latest/format_description.html. So
1012 if a notebook has empty metadata, we will try to parse it anyway.
1014 language = nb.get("metadata", {}).get("language_info", {}).get("name", None)
1015 if language is not None and language != "python":
1016 raise NothingChanged from None
1019 def format_ipynb_string(src_contents: str, *, fast: bool, mode: Mode) -> FileContent:
1020 """Format Jupyter notebook.
1022 Operate cell-by-cell, only on code cells, only for Python notebooks.
1023 If the ``.ipynb`` originally had a trailing newline, it'll be preserved.
1025 trailing_newline = src_contents[-1] == "\n"
1027 nb = json.loads(src_contents)
1028 validate_metadata(nb)
1029 for cell in nb["cells"]:
1030 if cell.get("cell_type", None) == "code":
1032 src = "".join(cell["source"])
1033 dst = format_cell(src, fast=fast, mode=mode)
1034 except NothingChanged:
1037 cell["source"] = dst.splitlines(keepends=True)
1040 dst_contents = json.dumps(nb, indent=1, ensure_ascii=False)
1041 if trailing_newline:
1042 dst_contents = dst_contents + "\n"
1045 raise NothingChanged
1048 def format_str(src_contents: str, *, mode: Mode) -> FileContent:
1049 """Reformat a string and return new contents.
1051 `mode` determines formatting options, such as how many characters per line are
1055 >>> print(black.format_str("def f(arg:str='')->None:...", mode=black.Mode()))
1056 def f(arg: str = "") -> None:
1059 A more complex example:
1062 ... black.format_str(
1063 ... "def f(arg:str='')->None: hey",
1064 ... mode=black.Mode(
1065 ... target_versions={black.TargetVersion.PY36},
1067 ... string_normalization=False,
1078 src_node = lib2to3_parse(src_contents.lstrip(), mode.target_versions)
1080 future_imports = get_future_imports(src_node)
1081 if mode.target_versions:
1082 versions = mode.target_versions
1084 versions = detect_target_versions(src_node, future_imports=future_imports)
1086 # TODO: fully drop support and this code hopefully in January 2022 :D
1087 if TargetVersion.PY27 in mode.target_versions or versions == {TargetVersion.PY27}:
1089 "DEPRECATION: Python 2 support will be removed in the first stable release "
1090 "expected in January 2022."
1092 err(msg, fg="yellow", bold=True)
1094 normalize_fmt_off(src_node)
1095 lines = LineGenerator(
1097 remove_u_prefix="unicode_literals" in future_imports
1098 or supports_feature(versions, Feature.UNICODE_LITERALS),
1100 elt = EmptyLineTracker(is_pyi=mode.is_pyi)
1101 empty_line = Line(mode=mode)
1103 split_line_features = {
1105 for feature in {Feature.TRAILING_COMMA_IN_CALL, Feature.TRAILING_COMMA_IN_DEF}
1106 if supports_feature(versions, feature)
1108 for current_line in lines.visit(src_node):
1109 dst_contents.append(str(empty_line) * after)
1110 before, after = elt.maybe_empty_lines(current_line)
1111 dst_contents.append(str(empty_line) * before)
1112 for line in transform_line(
1113 current_line, mode=mode, features=split_line_features
1115 dst_contents.append(str(line))
1116 return "".join(dst_contents)
1119 def decode_bytes(src: bytes) -> Tuple[FileContent, Encoding, NewLine]:
1120 """Return a tuple of (decoded_contents, encoding, newline).
1122 `newline` is either CRLF or LF but `decoded_contents` is decoded with
1123 universal newlines (i.e. only contains LF).
1125 srcbuf = io.BytesIO(src)
1126 encoding, lines = tokenize.detect_encoding(srcbuf.readline)
1128 return "", encoding, "\n"
1130 newline = "\r\n" if b"\r\n" == lines[0][-2:] else "\n"
1132 with io.TextIOWrapper(srcbuf, encoding) as tiow:
1133 return tiow.read(), encoding, newline
1136 def get_features_used( # noqa: C901
1137 node: Node, *, future_imports: Optional[Set[str]] = None
1139 """Return a set of (relatively) new Python features used in this file.
1141 Currently looking for:
1143 - underscores in numeric literals;
1144 - trailing commas after * or ** in function signatures and calls;
1145 - positional only arguments in function signatures and lambdas;
1146 - assignment expression;
1147 - relaxed decorator syntax;
1148 - usage of __future__ flags (annotations);
1149 - print / exec statements;
1151 features: Set[Feature] = set()
1154 FUTURE_FLAG_TO_FEATURE[future_import]
1155 for future_import in future_imports
1156 if future_import in FUTURE_FLAG_TO_FEATURE
1159 for n in node.pre_order():
1160 if is_string_token(n):
1161 value_head = n.value[:2]
1162 if value_head in {'f"', 'F"', "f'", "F'", "rf", "fr", "RF", "FR"}:
1163 features.add(Feature.F_STRINGS)
1165 elif n.type == token.NUMBER:
1166 assert isinstance(n, Leaf)
1168 features.add(Feature.NUMERIC_UNDERSCORES)
1169 elif n.value.endswith(("L", "l")):
1171 features.add(Feature.LONG_INT_LITERAL)
1172 elif len(n.value) >= 2 and n.value[0] == "0" and n.value[1].isdigit():
1173 # Python 2: 0123; 00123; ...
1174 if not all(char == "0" for char in n.value):
1175 # although we don't want to match 0000 or similar
1176 features.add(Feature.OCTAL_INT_LITERAL)
1178 elif n.type == token.SLASH:
1179 if n.parent and n.parent.type in {
1184 features.add(Feature.POS_ONLY_ARGUMENTS)
1186 elif n.type == token.COLONEQUAL:
1187 features.add(Feature.ASSIGNMENT_EXPRESSIONS)
1189 elif n.type == syms.decorator:
1190 if len(n.children) > 1 and not is_simple_decorator_expression(
1193 features.add(Feature.RELAXED_DECORATORS)
1196 n.type in {syms.typedargslist, syms.arglist}
1198 and n.children[-1].type == token.COMMA
1200 if n.type == syms.typedargslist:
1201 feature = Feature.TRAILING_COMMA_IN_DEF
1203 feature = Feature.TRAILING_COMMA_IN_CALL
1205 for ch in n.children:
1206 if ch.type in STARS:
1207 features.add(feature)
1209 if ch.type == syms.argument:
1210 for argch in ch.children:
1211 if argch.type in STARS:
1212 features.add(feature)
1215 n.type in {syms.return_stmt, syms.yield_expr}
1216 and len(n.children) >= 2
1217 and n.children[1].type == syms.testlist_star_expr
1218 and any(child.type == syms.star_expr for child in n.children[1].children)
1220 features.add(Feature.UNPACKING_ON_FLOW)
1223 n.type == syms.annassign
1224 and len(n.children) >= 4
1225 and n.children[3].type == syms.testlist_star_expr
1227 features.add(Feature.ANN_ASSIGN_EXTENDED_RHS)
1229 # Python 2 only features (for its deprecation) except for integers, see above
1230 elif n.type == syms.print_stmt:
1231 features.add(Feature.PRINT_STMT)
1232 elif n.type == syms.exec_stmt:
1233 features.add(Feature.EXEC_STMT)
1234 elif n.type == syms.tfpdef:
1235 # def set_position((x, y), value):
1237 features.add(Feature.AUTOMATIC_PARAMETER_UNPACKING)
1238 elif n.type == syms.except_clause:
1241 # except Exception, err:
1243 if len(n.children) >= 4:
1244 if n.children[-2].type == token.COMMA:
1245 features.add(Feature.COMMA_STYLE_EXCEPT)
1246 elif n.type == syms.raise_stmt:
1247 # raise Exception, "msg"
1248 if len(n.children) >= 4:
1249 if n.children[-2].type == token.COMMA:
1250 features.add(Feature.COMMA_STYLE_RAISE)
1251 elif n.type == token.BACKQUOTE:
1252 # `i'm surprised this ever existed`
1253 features.add(Feature.BACKQUOTE_REPR)
1258 def detect_target_versions(
1259 node: Node, *, future_imports: Optional[Set[str]] = None
1260 ) -> Set[TargetVersion]:
1261 """Detect the version to target based on the nodes used."""
1262 features = get_features_used(node, future_imports=future_imports)
1264 version for version in TargetVersion if features <= VERSION_TO_FEATURES[version]
1268 def get_future_imports(node: Node) -> Set[str]:
1269 """Return a set of __future__ imports in the file."""
1270 imports: Set[str] = set()
1272 def get_imports_from_children(children: List[LN]) -> Generator[str, None, None]:
1273 for child in children:
1274 if isinstance(child, Leaf):
1275 if child.type == token.NAME:
1278 elif child.type == syms.import_as_name:
1279 orig_name = child.children[0]
1280 assert isinstance(orig_name, Leaf), "Invalid syntax parsing imports"
1281 assert orig_name.type == token.NAME, "Invalid syntax parsing imports"
1282 yield orig_name.value
1284 elif child.type == syms.import_as_names:
1285 yield from get_imports_from_children(child.children)
1288 raise AssertionError("Invalid syntax parsing imports")
1290 for child in node.children:
1291 if child.type != syms.simple_stmt:
1294 first_child = child.children[0]
1295 if isinstance(first_child, Leaf):
1296 # Continue looking if we see a docstring; otherwise stop.
1298 len(child.children) == 2
1299 and first_child.type == token.STRING
1300 and child.children[1].type == token.NEWLINE
1306 elif first_child.type == syms.import_from:
1307 module_name = first_child.children[1]
1308 if not isinstance(module_name, Leaf) or module_name.value != "__future__":
1311 imports |= set(get_imports_from_children(first_child.children[3:]))
1318 def assert_equivalent(src: str, dst: str, *, pass_num: int = 1) -> None:
1319 """Raise AssertionError if `src` and `dst` aren't equivalent."""
1321 src_ast = parse_ast(src)
1322 except Exception as exc:
1323 raise AssertionError(
1324 f"cannot use --safe with this file; failed to parse source file: {exc}"
1328 dst_ast = parse_ast(dst)
1329 except Exception as exc:
1330 log = dump_to_file("".join(traceback.format_tb(exc.__traceback__)), dst)
1331 raise AssertionError(
1332 f"INTERNAL ERROR: Black produced invalid code on pass {pass_num}: {exc}. "
1333 "Please report a bug on https://github.com/psf/black/issues. "
1334 f"This invalid output might be helpful: {log}"
1337 src_ast_str = "\n".join(stringify_ast(src_ast))
1338 dst_ast_str = "\n".join(stringify_ast(dst_ast))
1339 if src_ast_str != dst_ast_str:
1340 log = dump_to_file(diff(src_ast_str, dst_ast_str, "src", "dst"))
1341 raise AssertionError(
1342 "INTERNAL ERROR: Black produced code that is not equivalent to the"
1343 f" source on pass {pass_num}. Please report a bug on "
1344 f"https://github.com/psf/black/issues. This diff might be helpful: {log}"
1348 def assert_stable(src: str, dst: str, mode: Mode) -> None:
1349 """Raise AssertionError if `dst` reformats differently the second time."""
1350 newdst = format_str(dst, mode=mode)
1354 diff(src, dst, "source", "first pass"),
1355 diff(dst, newdst, "first pass", "second pass"),
1357 raise AssertionError(
1358 "INTERNAL ERROR: Black produced different code on the second pass of the"
1359 " formatter. Please report a bug on https://github.com/psf/black/issues."
1360 f" This diff might be helpful: {log}"
1365 def nullcontext() -> Iterator[None]:
1366 """Return an empty context manager.
1368 To be used like `nullcontext` in Python 3.7.
1373 def patch_click() -> None:
1374 """Make Click not crash on Python 3.6 with LANG=C.
1376 On certain misconfigured environments, Python 3 selects the ASCII encoding as the
1377 default which restricts paths that it can access during the lifetime of the
1378 application. Click refuses to work in this scenario by raising a RuntimeError.
1380 In case of Black the likelihood that non-ASCII characters are going to be used in
1381 file paths is minimal since it's Python source code. Moreover, this crash was
1382 spurious on Python 3.7 thanks to PEP 538 and PEP 540.
1385 from click import core
1386 from click import _unicodefun
1387 except ModuleNotFoundError:
1390 for module in (core, _unicodefun):
1391 if hasattr(module, "_verify_python3_env"):
1392 module._verify_python3_env = lambda: None # type: ignore
1393 if hasattr(module, "_verify_python_env"):
1394 module._verify_python_env = lambda: None # type: ignore
1397 def patched_main() -> None:
1398 maybe_install_uvloop()
1404 if __name__ == "__main__":