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 click.core import ParameterSource
35 from dataclasses import replace
36 from mypy_extensions import mypyc_attr
38 from black.const import DEFAULT_LINE_LENGTH, DEFAULT_INCLUDES, DEFAULT_EXCLUDES
39 from black.const import STDIN_PLACEHOLDER
40 from black.nodes import STARS, syms, is_simple_decorator_expression
41 from black.nodes import is_string_token
42 from black.lines import Line, EmptyLineTracker
43 from black.linegen import transform_line, LineGenerator, LN
44 from black.comments import normalize_fmt_off
45 from black.mode import FUTURE_FLAG_TO_FEATURE, Mode, TargetVersion
46 from black.mode import Feature, supports_feature, VERSION_TO_FEATURES
47 from black.cache import read_cache, write_cache, get_cache_info, filter_cached, Cache
48 from black.concurrency import cancel, shutdown, maybe_install_uvloop
49 from black.output import dump_to_file, ipynb_diff, diff, color_diff, out, err
50 from black.report import Report, Changed, NothingChanged
51 from black.files import find_project_root, find_pyproject_toml, parse_pyproject_toml
52 from black.files import gen_python_files, get_gitignore, normalize_path_maybe_ignore
53 from black.files import wrap_stream_for_windows
54 from black.parsing import InvalidInput # noqa F401
55 from black.parsing import lib2to3_parse, parse_ast, stringify_ast
56 from black.handle_ipynb_magics import (
59 remove_trailing_semicolon,
60 put_trailing_semicolon_back,
63 jupyter_dependencies_are_installed,
68 from blib2to3.pytree import Node, Leaf
69 from blib2to3.pgen2 import token
71 from _black_version import version as __version__
73 COMPILED = Path(__file__).suffix in (".pyd", ".so")
81 class WriteBack(Enum):
89 def from_configuration(
90 cls, *, check: bool, diff: bool, color: bool = False
92 if check and not diff:
98 return cls.DIFF if diff else cls.YES
101 # Legacy name, left for integrations.
104 DEFAULT_WORKERS = os.cpu_count()
107 def read_pyproject_toml(
108 ctx: click.Context, param: click.Parameter, value: Optional[str]
110 """Inject Black configuration from "pyproject.toml" into defaults in `ctx`.
112 Returns the path to a successfully found and read configuration file, None
116 value = find_pyproject_toml(ctx.params.get("src", ()))
121 config = parse_pyproject_toml(value)
122 except (OSError, ValueError) as e:
123 raise click.FileError(
124 filename=value, hint=f"Error reading configuration file: {e}"
130 # Sanitize the values to be Click friendly. For more information please see:
131 # https://github.com/psf/black/issues/1458
132 # https://github.com/pallets/click/issues/1567
134 k: str(v) if not isinstance(v, (list, dict)) else v
135 for k, v in config.items()
138 target_version = config.get("target_version")
139 if target_version is not None and not isinstance(target_version, list):
140 raise click.BadOptionUsage(
141 "target-version", "Config key target-version must be a list"
144 default_map: Dict[str, Any] = {}
146 default_map.update(ctx.default_map)
147 default_map.update(config)
149 ctx.default_map = default_map
153 def target_version_option_callback(
154 c: click.Context, p: Union[click.Option, click.Parameter], v: Tuple[str, ...]
155 ) -> List[TargetVersion]:
156 """Compute the target versions from a --target-version flag.
158 This is its own function because mypy couldn't infer the type correctly
159 when it was a lambda, causing mypyc trouble.
161 return [TargetVersion[val.upper()] for val in v]
164 def re_compile_maybe_verbose(regex: str) -> Pattern[str]:
165 """Compile a regular expression string in `regex`.
167 If it contains newlines, use verbose mode.
170 regex = "(?x)" + regex
171 compiled: Pattern[str] = re.compile(regex)
177 param: click.Parameter,
178 value: Optional[str],
179 ) -> Optional[Pattern[str]]:
181 return re_compile_maybe_verbose(value) if value is not None else None
182 except re.error as e:
183 raise click.BadParameter(f"Not a valid regular expression: {e}") from None
187 context_settings={"help_option_names": ["-h", "--help"]},
188 # While Click does set this field automatically using the docstring, mypyc
189 # (annoyingly) strips 'em so we need to set it here too.
190 help="The uncompromising code formatter.",
192 @click.option("-c", "--code", type=str, help="Format the code passed in as a string.")
197 default=DEFAULT_LINE_LENGTH,
198 help="How many characters per line to allow.",
204 type=click.Choice([v.name.lower() for v in TargetVersion]),
205 callback=target_version_option_callback,
208 "Python versions that should be supported by Black's output. [default: per-file"
216 "Format all input files like typing stubs regardless of file extension (useful"
217 " when piping source on standard input)."
224 "Format all input files like Jupyter Notebooks regardless of file extension "
225 "(useful when piping source on standard input)."
230 "--skip-string-normalization",
232 help="Don't normalize string quotes or prefixes.",
236 "--skip-magic-trailing-comma",
238 help="Don't use trailing commas as a reason to split lines.",
241 "--experimental-string-processing",
245 "Experimental option that performs more normalization on string literals."
246 " Currently disabled because it leads to some crashes."
253 "Enable potentially disruptive style changes that will be added to Black's main"
254 " functionality in the next major release."
261 "Don't write the files back, just return the status. Return code 0 means"
262 " nothing would change. Return code 1 means some files would be reformatted."
263 " Return code 123 means there was an internal error."
269 help="Don't write the files back, just output a diff for each file on stdout.",
272 "--color/--no-color",
274 help="Show colored diff. Only applies when `--diff` is given.",
279 help="If --fast given, skip temporary sanity checks. [default: --safe]",
282 "--required-version",
285 "Require a specific version of Black to be running (useful for unifying results"
286 " across many environments e.g. with a pyproject.toml file)."
292 default=DEFAULT_INCLUDES,
293 callback=validate_regex,
295 "A regular expression that matches files and directories that should be"
296 " included on recursive searches. An empty value means all files are included"
297 " regardless of the name. Use forward slashes for directories on all platforms"
298 " (Windows, too). Exclusions are calculated first, inclusions later."
305 callback=validate_regex,
307 "A regular expression that matches files and directories that should be"
308 " excluded on recursive searches. An empty value means no paths are excluded."
309 " Use forward slashes for directories on all platforms (Windows, too)."
310 " Exclusions are calculated first, inclusions later. [default:"
311 f" {DEFAULT_EXCLUDES}]"
318 callback=validate_regex,
320 "Like --exclude, but adds additional files and directories on top of the"
321 " excluded ones. (Useful if you simply want to add to the default)"
327 callback=validate_regex,
329 "Like --exclude, but files and directories matching this regex will be "
330 "excluded even when they are passed explicitly as arguments."
337 "The name of the file when passing it through stdin. Useful to make "
338 "sure Black will respect --force-exclude option on some "
339 "editors that rely on using stdin."
345 type=click.IntRange(min=1),
346 default=DEFAULT_WORKERS,
348 help="Number of parallel workers",
355 "Don't emit non-error messages to stderr. Errors are still emitted; silence"
356 " those with 2>/dev/null."
364 "Also emit messages to stderr about files that were not changed or were ignored"
365 " due to exclusion patterns."
368 @click.version_option(
370 message=f"%(prog)s, %(version)s (compiled: {'yes' if COMPILED else 'no'})",
376 exists=True, file_okay=True, dir_okay=True, readable=True, allow_dash=True
392 callback=read_pyproject_toml,
393 help="Read configuration from FILE path.",
400 target_version: List[TargetVersion],
407 skip_string_normalization: bool,
408 skip_magic_trailing_comma: bool,
409 experimental_string_processing: bool,
413 required_version: Optional[str],
414 include: Pattern[str],
415 exclude: Optional[Pattern[str]],
416 extend_exclude: Optional[Pattern[str]],
417 force_exclude: Optional[Pattern[str]],
418 stdin_filename: Optional[str],
420 src: Tuple[str, ...],
421 config: Optional[str],
423 """The uncompromising code formatter."""
424 ctx.ensure_object(dict)
425 root, method = find_project_root(src) if code is None else (None, None)
426 ctx.obj["root"] = root
431 f"Identified `{root}` as project root containing a {method}.",
436 (normalize_path_maybe_ignore(Path(source), root), source)
439 srcs_string = ", ".join(
443 else f'\033[31m"{source} (skipping - invalid)"\033[34m'
444 for _norm, source in normalized
447 out(f"Sources to be formatted: {srcs_string}", fg="blue")
450 config_source = ctx.get_parameter_source("config")
451 if config_source in (ParameterSource.DEFAULT, ParameterSource.DEFAULT_MAP):
452 out("Using configuration from project root.", fg="blue")
454 out(f"Using configuration in '{config}'.", fg="blue")
456 error_msg = "Oh no! 💥 💔 💥"
457 if required_version and required_version != __version__:
459 f"{error_msg} The required version `{required_version}` does not match"
460 f" the running version `{__version__}`!"
464 err("Cannot pass both `pyi` and `ipynb` flags!")
467 write_back = WriteBack.from_configuration(check=check, diff=diff, color=color)
469 versions = set(target_version)
471 # We'll autodetect later.
474 target_versions=versions,
475 line_length=line_length,
478 string_normalization=not skip_string_normalization,
479 magic_trailing_comma=not skip_magic_trailing_comma,
480 experimental_string_processing=experimental_string_processing,
485 # Run in quiet mode by default with -c; the extra output isn't useful.
486 # You can still pass -v to get verbose output.
489 report = Report(check=check, diff=diff, quiet=quiet, verbose=verbose)
493 content=code, fast=fast, write_back=write_back, mode=mode, report=report
497 sources = get_sources(
504 extend_exclude=extend_exclude,
505 force_exclude=force_exclude,
507 stdin_filename=stdin_filename,
509 except GitWildMatchPatternError:
514 "No Python files are present to be formatted. Nothing to do 😴",
520 if len(sources) == 1:
524 write_back=write_back,
532 write_back=write_back,
538 if verbose or not quiet:
539 if code is None and (verbose or report.change_count or report.failure_count):
541 out(error_msg if report.return_code else "All done! ✨ 🍰 ✨")
543 click.echo(str(report), err=True)
544 ctx.exit(report.return_code)
550 src: Tuple[str, ...],
553 include: Pattern[str],
554 exclude: Optional[Pattern[str]],
555 extend_exclude: Optional[Pattern[str]],
556 force_exclude: Optional[Pattern[str]],
558 stdin_filename: Optional[str],
560 """Compute the set of files to be formatted."""
561 sources: Set[Path] = set()
562 path_empty(src, "No Path provided. Nothing to do 😴", quiet, verbose, ctx)
565 exclude = re_compile_maybe_verbose(DEFAULT_EXCLUDES)
566 gitignore = get_gitignore(ctx.obj["root"])
571 if s == "-" and stdin_filename:
572 p = Path(stdin_filename)
578 if is_stdin or p.is_file():
579 normalized_path = normalize_path_maybe_ignore(p, ctx.obj["root"], report)
580 if normalized_path is None:
583 normalized_path = "/" + normalized_path
584 # Hard-exclude any files that matches the `--force-exclude` regex.
586 force_exclude_match = force_exclude.search(normalized_path)
588 force_exclude_match = None
589 if force_exclude_match and force_exclude_match.group(0):
590 report.path_ignored(p, "matches the --force-exclude regular expression")
594 p = Path(f"{STDIN_PLACEHOLDER}{str(p)}")
596 if p.suffix == ".ipynb" and not jupyter_dependencies_are_installed(
597 verbose=verbose, quiet=quiet
620 err(f"invalid path: {s}")
625 src: Sized, msg: str, quiet: bool, verbose: bool, ctx: click.Context
628 Exit if there is no `src` provided for formatting
631 if verbose or not quiet:
637 content: str, fast: bool, write_back: WriteBack, mode: Mode, report: Report
640 Reformat and print out `content` without spawning child processes.
641 Similar to `reformat_one`, but for string content.
643 `fast`, `write_back`, and `mode` options are passed to
644 :func:`format_file_in_place` or :func:`format_stdin_to_stdout`.
646 path = Path("<string>")
649 if format_stdin_to_stdout(
650 content=content, fast=fast, write_back=write_back, mode=mode
652 changed = Changed.YES
653 report.done(path, changed)
654 except Exception as exc:
656 traceback.print_exc()
657 report.failed(path, str(exc))
661 src: Path, fast: bool, write_back: WriteBack, mode: Mode, report: "Report"
663 """Reformat a single file under `src` without spawning child processes.
665 `fast`, `write_back`, and `mode` options are passed to
666 :func:`format_file_in_place` or :func:`format_stdin_to_stdout`.
673 elif str(src).startswith(STDIN_PLACEHOLDER):
675 # Use the original name again in case we want to print something
677 src = Path(str(src)[len(STDIN_PLACEHOLDER) :])
682 if src.suffix == ".pyi":
683 mode = replace(mode, is_pyi=True)
684 elif src.suffix == ".ipynb":
685 mode = replace(mode, is_ipynb=True)
686 if format_stdin_to_stdout(fast=fast, write_back=write_back, mode=mode):
687 changed = Changed.YES
690 if write_back not in (WriteBack.DIFF, WriteBack.COLOR_DIFF):
691 cache = read_cache(mode)
692 res_src = src.resolve()
693 res_src_s = str(res_src)
694 if res_src_s in cache and cache[res_src_s] == get_cache_info(res_src):
695 changed = Changed.CACHED
696 if changed is not Changed.CACHED and format_file_in_place(
697 src, fast=fast, write_back=write_back, mode=mode
699 changed = Changed.YES
700 if (write_back is WriteBack.YES and changed is not Changed.CACHED) or (
701 write_back is WriteBack.CHECK and changed is Changed.NO
703 write_cache(cache, [src], mode)
704 report.done(src, changed)
705 except Exception as exc:
707 traceback.print_exc()
708 report.failed(src, str(exc))
711 # diff-shades depends on being to monkeypatch this function to operate. I know it's
712 # not ideal, but this shouldn't cause any issues ... hopefully. ~ichard26
713 @mypyc_attr(patchable=True)
717 write_back: WriteBack,
720 workers: Optional[int],
722 """Reformat multiple files using a ProcessPoolExecutor."""
724 loop = asyncio.get_event_loop()
725 worker_count = workers if workers is not None else DEFAULT_WORKERS
726 if sys.platform == "win32":
727 # Work around https://bugs.python.org/issue26903
728 assert worker_count is not None
729 worker_count = min(worker_count, 60)
731 executor = ProcessPoolExecutor(max_workers=worker_count)
732 except (ImportError, NotImplementedError, OSError):
733 # we arrive here if the underlying system does not support multi-processing
734 # like in AWS Lambda or Termux, in which case we gracefully fallback to
735 # a ThreadPoolExecutor with just a single worker (more workers would not do us
736 # any good due to the Global Interpreter Lock)
737 executor = ThreadPoolExecutor(max_workers=1)
740 loop.run_until_complete(
744 write_back=write_back,
753 if executor is not None:
757 async def schedule_formatting(
760 write_back: WriteBack,
763 loop: asyncio.AbstractEventLoop,
766 """Run formatting of `sources` in parallel using the provided `executor`.
768 (Use ProcessPoolExecutors for actual parallelism.)
770 `write_back`, `fast`, and `mode` options are passed to
771 :func:`format_file_in_place`.
774 if write_back not in (WriteBack.DIFF, WriteBack.COLOR_DIFF):
775 cache = read_cache(mode)
776 sources, cached = filter_cached(cache, sources)
777 for src in sorted(cached):
778 report.done(src, Changed.CACHED)
783 sources_to_cache = []
785 if write_back in (WriteBack.DIFF, WriteBack.COLOR_DIFF):
786 # For diff output, we need locks to ensure we don't interleave output
787 # from different processes.
789 lock = manager.Lock()
791 asyncio.ensure_future(
792 loop.run_in_executor(
793 executor, format_file_in_place, src, fast, mode, write_back, lock
796 for src in sorted(sources)
798 pending = tasks.keys()
800 loop.add_signal_handler(signal.SIGINT, cancel, pending)
801 loop.add_signal_handler(signal.SIGTERM, cancel, pending)
802 except NotImplementedError:
803 # There are no good alternatives for these on Windows.
806 done, _ = await asyncio.wait(pending, return_when=asyncio.FIRST_COMPLETED)
808 src = tasks.pop(task)
810 cancelled.append(task)
811 elif task.exception():
812 report.failed(src, str(task.exception()))
814 changed = Changed.YES if task.result() else Changed.NO
815 # If the file was written back or was successfully checked as
816 # well-formatted, store this information in the cache.
817 if write_back is WriteBack.YES or (
818 write_back is WriteBack.CHECK and changed is Changed.NO
820 sources_to_cache.append(src)
821 report.done(src, changed)
823 if sys.version_info >= (3, 7):
824 await asyncio.gather(*cancelled, return_exceptions=True)
826 await asyncio.gather(*cancelled, loop=loop, return_exceptions=True)
828 write_cache(cache, sources_to_cache, mode)
831 def format_file_in_place(
835 write_back: WriteBack = WriteBack.NO,
836 lock: Any = None, # multiprocessing.Manager().Lock() is some crazy proxy
838 """Format file under `src` path. Return True if changed.
840 If `write_back` is DIFF, write a diff to stdout. If it is YES, write reformatted
842 `mode` and `fast` options are passed to :func:`format_file_contents`.
844 if src.suffix == ".pyi":
845 mode = replace(mode, is_pyi=True)
846 elif src.suffix == ".ipynb":
847 mode = replace(mode, is_ipynb=True)
849 then = datetime.utcfromtimestamp(src.stat().st_mtime)
850 with open(src, "rb") as buf:
851 src_contents, encoding, newline = decode_bytes(buf.read())
853 dst_contents = format_file_contents(src_contents, fast=fast, mode=mode)
854 except NothingChanged:
856 except JSONDecodeError:
858 f"File '{src}' cannot be parsed as valid Jupyter notebook."
861 if write_back == WriteBack.YES:
862 with open(src, "w", encoding=encoding, newline=newline) as f:
863 f.write(dst_contents)
864 elif write_back in (WriteBack.DIFF, WriteBack.COLOR_DIFF):
865 now = datetime.utcnow()
866 src_name = f"{src}\t{then} +0000"
867 dst_name = f"{src}\t{now} +0000"
869 diff_contents = ipynb_diff(src_contents, dst_contents, src_name, dst_name)
871 diff_contents = diff(src_contents, dst_contents, src_name, dst_name)
873 if write_back == WriteBack.COLOR_DIFF:
874 diff_contents = color_diff(diff_contents)
876 with lock or nullcontext():
877 f = io.TextIOWrapper(
883 f = wrap_stream_for_windows(f)
884 f.write(diff_contents)
890 def format_stdin_to_stdout(
893 content: Optional[str] = None,
894 write_back: WriteBack = WriteBack.NO,
897 """Format file on stdin. Return True if changed.
899 If content is None, it's read from sys.stdin.
901 If `write_back` is YES, write reformatted code back to stdout. If it is DIFF,
902 write a diff to stdout. The `mode` argument is passed to
903 :func:`format_file_contents`.
905 then = datetime.utcnow()
908 src, encoding, newline = decode_bytes(sys.stdin.buffer.read())
910 src, encoding, newline = content, "utf-8", ""
914 dst = format_file_contents(src, fast=fast, mode=mode)
917 except NothingChanged:
921 f = io.TextIOWrapper(
922 sys.stdout.buffer, encoding=encoding, newline=newline, write_through=True
924 if write_back == WriteBack.YES:
925 # Make sure there's a newline after the content
926 if dst and dst[-1] != "\n":
929 elif write_back in (WriteBack.DIFF, WriteBack.COLOR_DIFF):
930 now = datetime.utcnow()
931 src_name = f"STDIN\t{then} +0000"
932 dst_name = f"STDOUT\t{now} +0000"
933 d = diff(src, dst, src_name, dst_name)
934 if write_back == WriteBack.COLOR_DIFF:
936 f = wrap_stream_for_windows(f)
941 def check_stability_and_equivalence(
942 src_contents: str, dst_contents: str, *, mode: Mode
944 """Perform stability and equivalence checks.
946 Raise AssertionError if source and destination contents are not
947 equivalent, or if a second pass of the formatter would format the
950 assert_equivalent(src_contents, dst_contents)
952 # Forced second pass to work around optional trailing commas (becoming
953 # forced trailing commas on pass 2) interacting differently with optional
954 # parentheses. Admittedly ugly.
955 dst_contents_pass2 = format_str(dst_contents, mode=mode)
956 if dst_contents != dst_contents_pass2:
957 dst_contents = dst_contents_pass2
958 assert_equivalent(src_contents, dst_contents, pass_num=2)
959 assert_stable(src_contents, dst_contents, mode=mode)
960 # Note: no need to explicitly call `assert_stable` if `dst_contents` was
961 # the same as `dst_contents_pass2`.
964 def format_file_contents(src_contents: str, *, fast: bool, mode: Mode) -> FileContent:
965 """Reformat contents of a file and return new contents.
967 If `fast` is False, additionally confirm that the reformatted code is
968 valid by calling :func:`assert_equivalent` and :func:`assert_stable` on it.
969 `mode` is passed to :func:`format_str`.
971 if not src_contents.strip():
975 dst_contents = format_ipynb_string(src_contents, fast=fast, mode=mode)
977 dst_contents = format_str(src_contents, mode=mode)
978 if src_contents == dst_contents:
981 if not fast and not mode.is_ipynb:
982 # Jupyter notebooks will already have been checked above.
983 check_stability_and_equivalence(src_contents, dst_contents, mode=mode)
987 def validate_cell(src: str) -> None:
988 """Check that cell does not already contain TransformerManager transformations,
989 or non-Python cell magics, which might cause tokenizer_rt to break because of
992 If a cell contains ``!ls``, then it'll be transformed to
993 ``get_ipython().system('ls')``. However, if the cell originally contained
994 ``get_ipython().system('ls')``, then it would get transformed in the same way:
996 >>> TransformerManager().transform_cell("get_ipython().system('ls')")
997 "get_ipython().system('ls')\n"
998 >>> TransformerManager().transform_cell("!ls")
999 "get_ipython().system('ls')\n"
1001 Due to the impossibility of safely roundtripping in such situations, cells
1002 containing transformed magics will be ignored.
1004 if any(transformed_magic in src for transformed_magic in TRANSFORMED_MAGICS):
1005 raise NothingChanged
1006 if src[:2] == "%%" and src.split()[0][2:] not in PYTHON_CELL_MAGICS:
1007 raise NothingChanged
1010 def format_cell(src: str, *, fast: bool, mode: Mode) -> str:
1011 """Format code in given cell of Jupyter notebook.
1015 - if cell has trailing semicolon, remove it;
1016 - if cell has IPython magics, mask them;
1018 - reinstate IPython magics;
1019 - reinstate trailing semicolon (if originally present);
1020 - strip trailing newlines.
1022 Cells with syntax errors will not be processed, as they
1023 could potentially be automagics or multi-line magics, which
1024 are currently not supported.
1027 src_without_trailing_semicolon, has_trailing_semicolon = remove_trailing_semicolon(
1031 masked_src, replacements = mask_cell(src_without_trailing_semicolon)
1033 raise NothingChanged from None
1034 masked_dst = format_str(masked_src, mode=mode)
1036 check_stability_and_equivalence(masked_src, masked_dst, mode=mode)
1037 dst_without_trailing_semicolon = unmask_cell(masked_dst, replacements)
1038 dst = put_trailing_semicolon_back(
1039 dst_without_trailing_semicolon, has_trailing_semicolon
1041 dst = dst.rstrip("\n")
1043 raise NothingChanged from None
1047 def validate_metadata(nb: MutableMapping[str, Any]) -> None:
1048 """If notebook is marked as non-Python, don't format it.
1050 All notebook metadata fields are optional, see
1051 https://nbformat.readthedocs.io/en/latest/format_description.html. So
1052 if a notebook has empty metadata, we will try to parse it anyway.
1054 language = nb.get("metadata", {}).get("language_info", {}).get("name", None)
1055 if language is not None and language != "python":
1056 raise NothingChanged from None
1059 def format_ipynb_string(src_contents: str, *, fast: bool, mode: Mode) -> FileContent:
1060 """Format Jupyter notebook.
1062 Operate cell-by-cell, only on code cells, only for Python notebooks.
1063 If the ``.ipynb`` originally had a trailing newline, it'll be preserved.
1065 trailing_newline = src_contents[-1] == "\n"
1067 nb = json.loads(src_contents)
1068 validate_metadata(nb)
1069 for cell in nb["cells"]:
1070 if cell.get("cell_type", None) == "code":
1072 src = "".join(cell["source"])
1073 dst = format_cell(src, fast=fast, mode=mode)
1074 except NothingChanged:
1077 cell["source"] = dst.splitlines(keepends=True)
1080 dst_contents = json.dumps(nb, indent=1, ensure_ascii=False)
1081 if trailing_newline:
1082 dst_contents = dst_contents + "\n"
1085 raise NothingChanged
1088 def format_str(src_contents: str, *, mode: Mode) -> FileContent:
1089 """Reformat a string and return new contents.
1091 `mode` determines formatting options, such as how many characters per line are
1095 >>> print(black.format_str("def f(arg:str='')->None:...", mode=black.Mode()))
1096 def f(arg: str = "") -> None:
1099 A more complex example:
1102 ... black.format_str(
1103 ... "def f(arg:str='')->None: hey",
1104 ... mode=black.Mode(
1105 ... target_versions={black.TargetVersion.PY36},
1107 ... string_normalization=False,
1118 src_node = lib2to3_parse(src_contents.lstrip(), mode.target_versions)
1120 future_imports = get_future_imports(src_node)
1121 if mode.target_versions:
1122 versions = mode.target_versions
1124 versions = detect_target_versions(src_node, future_imports=future_imports)
1126 normalize_fmt_off(src_node)
1127 lines = LineGenerator(mode=mode)
1128 elt = EmptyLineTracker(is_pyi=mode.is_pyi)
1129 empty_line = Line(mode=mode)
1131 split_line_features = {
1133 for feature in {Feature.TRAILING_COMMA_IN_CALL, Feature.TRAILING_COMMA_IN_DEF}
1134 if supports_feature(versions, feature)
1136 for current_line in lines.visit(src_node):
1137 dst_contents.append(str(empty_line) * after)
1138 before, after = elt.maybe_empty_lines(current_line)
1139 dst_contents.append(str(empty_line) * before)
1140 for line in transform_line(
1141 current_line, mode=mode, features=split_line_features
1143 dst_contents.append(str(line))
1144 return "".join(dst_contents)
1147 def decode_bytes(src: bytes) -> Tuple[FileContent, Encoding, NewLine]:
1148 """Return a tuple of (decoded_contents, encoding, newline).
1150 `newline` is either CRLF or LF but `decoded_contents` is decoded with
1151 universal newlines (i.e. only contains LF).
1153 srcbuf = io.BytesIO(src)
1154 encoding, lines = tokenize.detect_encoding(srcbuf.readline)
1156 return "", encoding, "\n"
1158 newline = "\r\n" if b"\r\n" == lines[0][-2:] else "\n"
1160 with io.TextIOWrapper(srcbuf, encoding) as tiow:
1161 return tiow.read(), encoding, newline
1164 def get_features_used( # noqa: C901
1165 node: Node, *, future_imports: Optional[Set[str]] = None
1167 """Return a set of (relatively) new Python features used in this file.
1169 Currently looking for:
1171 - underscores in numeric literals;
1172 - trailing commas after * or ** in function signatures and calls;
1173 - positional only arguments in function signatures and lambdas;
1174 - assignment expression;
1175 - relaxed decorator syntax;
1176 - usage of __future__ flags (annotations);
1177 - print / exec statements;
1179 features: Set[Feature] = set()
1182 FUTURE_FLAG_TO_FEATURE[future_import]
1183 for future_import in future_imports
1184 if future_import in FUTURE_FLAG_TO_FEATURE
1187 for n in node.pre_order():
1188 if is_string_token(n):
1189 value_head = n.value[:2]
1190 if value_head in {'f"', 'F"', "f'", "F'", "rf", "fr", "RF", "FR"}:
1191 features.add(Feature.F_STRINGS)
1193 elif n.type == token.NUMBER:
1194 assert isinstance(n, Leaf)
1196 features.add(Feature.NUMERIC_UNDERSCORES)
1198 elif n.type == token.SLASH:
1199 if n.parent and n.parent.type in {
1204 features.add(Feature.POS_ONLY_ARGUMENTS)
1206 elif n.type == token.COLONEQUAL:
1207 features.add(Feature.ASSIGNMENT_EXPRESSIONS)
1209 elif n.type == syms.decorator:
1210 if len(n.children) > 1 and not is_simple_decorator_expression(
1213 features.add(Feature.RELAXED_DECORATORS)
1216 n.type in {syms.typedargslist, syms.arglist}
1218 and n.children[-1].type == token.COMMA
1220 if n.type == syms.typedargslist:
1221 feature = Feature.TRAILING_COMMA_IN_DEF
1223 feature = Feature.TRAILING_COMMA_IN_CALL
1225 for ch in n.children:
1226 if ch.type in STARS:
1227 features.add(feature)
1229 if ch.type == syms.argument:
1230 for argch in ch.children:
1231 if argch.type in STARS:
1232 features.add(feature)
1235 n.type in {syms.return_stmt, syms.yield_expr}
1236 and len(n.children) >= 2
1237 and n.children[1].type == syms.testlist_star_expr
1238 and any(child.type == syms.star_expr for child in n.children[1].children)
1240 features.add(Feature.UNPACKING_ON_FLOW)
1243 n.type == syms.annassign
1244 and len(n.children) >= 4
1245 and n.children[3].type == syms.testlist_star_expr
1247 features.add(Feature.ANN_ASSIGN_EXTENDED_RHS)
1252 def detect_target_versions(
1253 node: Node, *, future_imports: Optional[Set[str]] = None
1254 ) -> Set[TargetVersion]:
1255 """Detect the version to target based on the nodes used."""
1256 features = get_features_used(node, future_imports=future_imports)
1258 version for version in TargetVersion if features <= VERSION_TO_FEATURES[version]
1262 def get_future_imports(node: Node) -> Set[str]:
1263 """Return a set of __future__ imports in the file."""
1264 imports: Set[str] = set()
1266 def get_imports_from_children(children: List[LN]) -> Generator[str, None, None]:
1267 for child in children:
1268 if isinstance(child, Leaf):
1269 if child.type == token.NAME:
1272 elif child.type == syms.import_as_name:
1273 orig_name = child.children[0]
1274 assert isinstance(orig_name, Leaf), "Invalid syntax parsing imports"
1275 assert orig_name.type == token.NAME, "Invalid syntax parsing imports"
1276 yield orig_name.value
1278 elif child.type == syms.import_as_names:
1279 yield from get_imports_from_children(child.children)
1282 raise AssertionError("Invalid syntax parsing imports")
1284 for child in node.children:
1285 if child.type != syms.simple_stmt:
1288 first_child = child.children[0]
1289 if isinstance(first_child, Leaf):
1290 # Continue looking if we see a docstring; otherwise stop.
1292 len(child.children) == 2
1293 and first_child.type == token.STRING
1294 and child.children[1].type == token.NEWLINE
1300 elif first_child.type == syms.import_from:
1301 module_name = first_child.children[1]
1302 if not isinstance(module_name, Leaf) or module_name.value != "__future__":
1305 imports |= set(get_imports_from_children(first_child.children[3:]))
1312 def assert_equivalent(src: str, dst: str, *, pass_num: int = 1) -> None:
1313 """Raise AssertionError if `src` and `dst` aren't equivalent."""
1315 src_ast = parse_ast(src)
1316 except Exception as exc:
1317 raise AssertionError(
1318 f"cannot use --safe with this file; failed to parse source file: {exc}"
1322 dst_ast = parse_ast(dst)
1323 except Exception as exc:
1324 log = dump_to_file("".join(traceback.format_tb(exc.__traceback__)), dst)
1325 raise AssertionError(
1326 f"INTERNAL ERROR: Black produced invalid code on pass {pass_num}: {exc}. "
1327 "Please report a bug on https://github.com/psf/black/issues. "
1328 f"This invalid output might be helpful: {log}"
1331 src_ast_str = "\n".join(stringify_ast(src_ast))
1332 dst_ast_str = "\n".join(stringify_ast(dst_ast))
1333 if src_ast_str != dst_ast_str:
1334 log = dump_to_file(diff(src_ast_str, dst_ast_str, "src", "dst"))
1335 raise AssertionError(
1336 "INTERNAL ERROR: Black produced code that is not equivalent to the"
1337 f" source on pass {pass_num}. Please report a bug on "
1338 f"https://github.com/psf/black/issues. This diff might be helpful: {log}"
1342 def assert_stable(src: str, dst: str, mode: Mode) -> None:
1343 """Raise AssertionError if `dst` reformats differently the second time."""
1344 newdst = format_str(dst, mode=mode)
1348 diff(src, dst, "source", "first pass"),
1349 diff(dst, newdst, "first pass", "second pass"),
1351 raise AssertionError(
1352 "INTERNAL ERROR: Black produced different code on the second pass of the"
1353 " formatter. Please report a bug on https://github.com/psf/black/issues."
1354 f" This diff might be helpful: {log}"
1359 def nullcontext() -> Iterator[None]:
1360 """Return an empty context manager.
1362 To be used like `nullcontext` in Python 3.7.
1367 def patch_click() -> None:
1368 """Make Click not crash on Python 3.6 with LANG=C.
1370 On certain misconfigured environments, Python 3 selects the ASCII encoding as the
1371 default which restricts paths that it can access during the lifetime of the
1372 application. Click refuses to work in this scenario by raising a RuntimeError.
1374 In case of Black the likelihood that non-ASCII characters are going to be used in
1375 file paths is minimal since it's Python source code. Moreover, this crash was
1376 spurious on Python 3.7 thanks to PEP 538 and PEP 540.
1379 from click import core
1380 from click import _unicodefun
1381 except ModuleNotFoundError:
1384 for module in (core, _unicodefun):
1385 if hasattr(module, "_verify_python3_env"):
1386 module._verify_python3_env = lambda: None # type: ignore
1387 if hasattr(module, "_verify_python_env"):
1388 module._verify_python_env = lambda: None # type: ignore
1391 def patched_main() -> None:
1392 maybe_install_uvloop()
1398 if __name__ == "__main__":