Ejemplo n.º 1
0
def run_black(src: Path, src_contents: str, black_args: BlackArgs) -> List[str]:
    """Run the black formatter for the Python source code given as a string

    Return lines of the original file as well as the formatted content.

    :param src: The originating file path for the source code
    :param src_contents: The source code as a string
    :param black_args: Command-line arguments to send to ``black.FileMode``

    """
    config = black_args.pop("config", None)
    combined_args = read_black_config(src, config)
    combined_args.update(black_args)

    effective_args = BlackModeAttributes()
    if "line_length" in combined_args:
        effective_args["line_length"] = combined_args["line_length"]
    if "skip_string_normalization" in combined_args:
        # The ``black`` command line argument is
        # ``--skip-string-normalization``, but the parameter for
        # ``black.Mode`` needs to be the opposite boolean of
        # ``skip-string-normalization``, hence the inverse boolean
        effective_args["string_normalization"] = not combined_args[
            "skip_string_normalization"
        ]

    # Override defaults and pyproject.toml settings if they've been specified
    # from the command line arguments
    mode = Mode(**effective_args)

    dst_contents = format_str(src_contents, mode=mode)
    dst_lines: List[str] = dst_contents.splitlines()
    return dst_lines
Ejemplo n.º 2
0
def file(node, filename, mode="a", skip_black=False):
    """
    Convert AST to a file

    :param node: AST node
    :type node: ```Union[Module, ClassDef, FunctionDef]```

    :param filename: emit to this file
    :type filename: ```str```

    :param mode: Mode to open the file in, defaults to append
    :type mode: ```str```

    :param skip_black: Skip formatting with black
    :type skip_black: ```bool```

    :return: None
    :rtype: ```NoneType```
    """
    if isinstance(node, (ClassDef, FunctionDef)):
        node = Module(body=[node], type_ignores=[], stmt=None)
    src = to_code(node)
    if not skip_black:
        src = format_str(
            src,
            mode=Mode(
                target_versions=set(),
                line_length=119,
                is_pyi=False,
                string_normalization=False,
            ),
        )
    with open(filename, mode) as f:
        f.write(src)
Ejemplo n.º 3
0
def read_black_config(path: Optional[PathLike]) -> Mode:
    """Parse Black configuration from provided toml."""
    black_mode = Mode(line_length=DEFAULT_LINE_LENGTH)
    if path is None:
        return black_mode
    if not Path(path).is_file():
        raise FileNotFoundError(f"{path} is not a file.")

    try:
        pyproject_toml = toml.load(path)
        config = pyproject_toml.get("tool", {}).get("black", {})
    except toml.TomlDecodeError as error:
        raise MalformattedToml(error)

    valid_black_filemode_params = sorted(
        [field.name for field in fields(Mode)])

    for key, val in config.items():
        # this is due to FileMode param being string_normalise, but CLI being
        # skip_string_normalise - https://github.com/snakemake/snakefmt/issues/73
        if key.startswith("skip"):
            key = key[5:]
            val = not val

        key = key.replace("-", "_")
        if key not in valid_black_filemode_params:
            continue

        setattr(black_mode, key, val)
    return black_mode
Ejemplo n.º 4
0
def run_ast_test(test_case_instance, gen_ast, gold, skip_black=False):
    """
    Compares `gen_ast` with `gold` standard

    :param test_case_instance: instance of `TestCase`
    :type test_case_instance: ```unittest.TestCase```

    :param gen_ast: generated AST
    :type gen_ast: ```Union[ast.Module, ast.ClassDef, ast.FunctionDef]```

    :param skip_black: Whether to skip formatting with black. Turned off for performance, turn on for pretty debug.
    :type skip_black: ```bool```

    :param gold: mocked AST
    :type gold: ```Union[ast.Module, ast.ClassDef, ast.FunctionDef]```
    """
    if isinstance(gen_ast, str):
        gen_ast = ast.parse(gen_ast).body[0]

    assert gen_ast is not None, "gen_ast is None"
    assert gold is not None, "gold is None"

    gen_ast = deepcopy(gen_ast)
    gold = deepcopy(gold)

    # if reindent_docstring:
    #           gen_docstring = ast.get_docstring(gen_ast)
    #           if gen_docstring is not None:
    #               gen_ast.body[0] = set_value(
    #                   "\n{}".format(indent(cleandoc(gen_docstring), tab))
    #               )
    #           gold.body[0] = set_value(
    #               "\n{}".format(indent(ast.get_docstring(gold, clean=True), tab))
    #           )

    # from meta.asttools import print_ast
    #
    # print("#gen")
    # print_ast(gen_ast)
    # print("#gold")
    # print_ast(gold)

    test_case_instance.assertEqual(*map(
        identity if skip_black else partial(
            format_str,
            mode=Mode(
                target_versions=set(),
                line_length=60,
                is_pyi=False,
                string_normalization=False,
            ),
        ),
        map(source_transformer.to_code, (gold, gen_ast)),
    ))

    test_case_instance.assertTrue(cmp_ast(gen_ast, gold),
                                  "Generated AST doesn't match reference AST")
Ejemplo n.º 5
0
def _parse_pyproject_config(context: click.Context, param: click.Parameter,
                            value: Optional[str]) -> Mode:
    if not value:
        pyproject_toml = find_pyproject_toml(
            tuple(context.params.get("files", (".", ))))
        value = pyproject_toml if pyproject_toml else None
    if value:
        try:
            pyproject_toml = toml.load(value)
            config = pyproject_toml.get("tool", {}).get("docstrfmt", {})
            config = {
                k.replace("--", "").replace("-", "_"): v
                for k, v in config.items()
            }
        except (OSError, ValueError) as e:  # pragma: no cover
            raise click.FileError(
                filename=value, hint=f"Error reading configuration file: {e}")

        if config:
            for key in ["exclude", "extend_exclude", "files"]:
                config_value = config.get(key)
                if config_value is not None and not isinstance(
                        config_value, list):
                    raise click.BadOptionUsage(
                        key, f"Config key {key} must be a list")
            params = {}
            if context.params is not None:
                params.update(context.params)
            params.update(config)
            context.params = params

        black_config = parse_pyproject_toml(value)
        black_config.pop("exclude", None)
        black_config.pop("extend_exclude", None)
        target_version = black_config.pop("target_version", ["PY37"])
        if target_version:
            target_version = set(
                getattr(TargetVersion, version.upper())
                for version in target_version
                if hasattr(TargetVersion, version.upper()))
        black_config["target_versions"] = target_version
        return Mode(**black_config)
    else:
        return Mode(line_length=88)
Ejemplo n.º 6
0
def format_python_black(file_path, file_content, **kwargs):
    from black import format_file_contents, Mode, NothingChanged, InvalidInput

    try:
        return format_file_contents(file_content, fast=False, mode=Mode())
    except NothingChanged:
        return file_content
    except InvalidInput:
        logger.warning("{} is not valid python code.".format(file_path))
        return file_content
Ejemplo n.º 7
0
def run_ast_test(test_case_instance, gen_ast, gold, skip_black=False):
    """
    Compares `gen_ast` with `gold` standard

    :param test_case_instance: instance of `TestCase`
    :type test_case_instance: ```unittest.TestCase```

    :param gen_ast: generated AST
    :type gen_ast: ```Union[ast.Module, ast.ClassDef, ast.FunctionDef]```

    :param gold: mocked AST
    :type gold: ```Union[ast.Module, ast.ClassDef, ast.FunctionDef]```

    :param skip_black: Whether to skip black
    :type skip_black: ```bool```
    """
    if isinstance(gen_ast, str):
        gen_ast = ast.parse(gen_ast).body[0]

    assert gen_ast is not None, "gen_ast is None"
    assert gold is not None, "gold is None"

    gen_ast = deepcopy(gen_ast)
    gold = deepcopy(gold)

    if hasattr(gen_ast, "body") and len(gen_ast.body) > 0:
        gen_docstring = ast.get_docstring(gen_ast)
        gold_docstring = ast.get_docstring(gold)
        if gen_docstring is not None and gold_docstring is not None:
            test_case_instance.assertEqual(gold_docstring.strip(),
                                           gen_docstring.strip())
            # Following test issue with docstring indentation, remove them from the AST, as symmetry has been confirmed
            gen_ast.body.pop(0)
            gold.body.pop(0)

    test_case_instance.assertEqual(*map(
        partial(
            (lambda _, **kwargs: _) if skip_black else format_str,
            mode=Mode(
                target_versions=set(),
                line_length=60,
                is_pyi=False,
                string_normalization=False,
            ),
        ),
        map(doctrans.source_transformer.to_code, (gold, gen_ast)),
    ))

    # from meta.asttools import print_ast
    # print_ast(gen_ast)
    # print_ast(gold)
    test_case_instance.assertTrue(cmp_ast(gen_ast, gold),
                                  "Generated AST doesn't match reference AST")
Ejemplo n.º 8
0
    def __str__(self) -> str:
        code = cst.Module(body=[self.stmt]).code
        if self.style:
            try:
                from black import Mode, format_str
            except ImportError:
                raise ImportError("Must install black to restyle code.")

            code = format_str(code, mode=Mode())
        if code.endswith("\n"):
            # Strip trailing newline without stripping deliberate ones.
            code = code[:-1]
        return code
Ejemplo n.º 9
0
def black_mode():
    from black import Mode, parse_pyproject_toml, target_version_option_callback

    config = parse_pyproject_toml(AIRFLOW_BREEZE_SOURCES_PATH / "pyproject.toml")

    target_versions = set(
        target_version_option_callback(None, None, tuple(config.get('target_version', ()))),
    )

    return Mode(
        target_versions=target_versions,
        line_length=bool(config.get('line_length', Mode.line_length)),
        is_pyi=bool(config.get('is_pyi', Mode.is_pyi)),
        string_normalization=not bool(config.get('skip_string_normalization', not Mode.string_normalization)),
        preview=bool(config.get('preview', Mode.preview)),
    )
Ejemplo n.º 10
0
def black_mode():
    from black import Mode, parse_pyproject_toml, target_version_option_callback

    config = parse_pyproject_toml(
        Path(get_breeze_pyproject_toml_dir(), "pyproject.toml"))

    target_versions = set(
        target_version_option_callback(None, None,
                                       tuple(config.get('target_version',
                                                        ()))), )

    return Mode(
        target_versions=target_versions,
        line_length=bool(config.get('line_length', Mode.line_length)),
        is_pyi=bool(config.get('is_pyi', Mode.is_pyi)),
        string_normalization=not bool(
            config.get('skip_string_normalization',
                       not Mode.string_normalization)),
        experimental_string_processing=bool(
            config.get('experimental_string_processing',
                       Mode.experimental_string_processing)),
    )
Ejemplo n.º 11
0
    NothingChanged,
    format_cell,
    format_file_contents,
    format_file_in_place,
)
import pytest
from black import Mode
from _pytest.monkeypatch import MonkeyPatch
from tests.util import DATA_DIR

pytestmark = pytest.mark.jupyter
pytest.importorskip("IPython", reason="IPython is an optional dependency")
pytest.importorskip("tokenize_rt",
                    reason="tokenize-rt is an optional dependency")

JUPYTER_MODE = Mode(is_ipynb=True)

EMPTY_CONFIG = DATA_DIR / "empty_pyproject.toml"

runner = CliRunner()


def test_noop() -> None:
    src = 'foo = "a"'
    with pytest.raises(NothingChanged):
        format_cell(src, fast=True, mode=JUPYTER_MODE)


@pytest.mark.parametrize("fast", [True, False])
def test_trailing_semicolon(fast: bool) -> None:
    src = 'foo = "a" ;'
Ejemplo n.º 12
0
def black_str_formatter(str_to_format: str) -> str:
    """
    The passed string is valid python that is returned with black formatting
    applied.
    """
    return format_str(str_to_format, mode=Mode())
Ejemplo n.º 13
0
 def __str__(self):
     try:
         return format_file_contents(self._json_str, fast=True, mode=Mode())
     except:
         return pformat(json.loads(self._json_str), indent=2)
Ejemplo n.º 14
0
import ast
from contextlib import suppress
from pathlib import Path
from typing import Iterable, Tuple, Dict

import astor
from black import Mode, TargetVersion, format_file_contents, NothingChanged

BLACK_MODE = Mode(
    target_versions={TargetVersion.PY36},
    line_length=88,  # default value
    is_pyi=False,  # for rendering type stubs
    string_normalization=True,
    experimental_string_processing=
    True,  # n.b. noted to occasionally cause crashes
)


def black_format(s: str):
    """Perform code formatting on unformatted source code."""
    return format_file_contents(
        s,
        fast=False,
        mode=BLACK_MODE,
    )


def render_module(module: ast.Module) -> str:
    """Transform AST module into source code."""
    source = astor.to_source(module)
    with suppress(NothingChanged):
Ejemplo n.º 15
0
def main(
    context: Context,
    mode: Mode,
    raw_input: str,
    raw_output: bool,
    verbose: int,
    line_length: int,
    file_type: str,
    check: bool,
    include_txt: bool,
    exclude: List[str],
    extend_exclude: List[str],
    quiet: bool,
    docstring_trailing_line: bool,
    files: List[str],
) -> None:
    reporter.level = verbose
    if "-" in files and len(files) > 1:
        reporter.error("ValueError: stdin can not be used with other paths")
        context.exit(2)
    if quiet or raw_output or files == ["-"]:
        reporter.level = -1
    misformatted_files = set()

    if line_length != 88:
        mode.line_length = line_length
    error_count = 0
    if raw_input:
        manager = Manager(reporter, mode, docstring_trailing_line)
        file = "<raw_input>"
        check = False
        try:
            misformatted = False
            if file_type == "py":
                misformatted, _ = _process_python(check, file, raw_input,
                                                  line_length, manager, True)
            elif file_type == "rst":
                misformatted, _ = _process_rst(check, file, raw_input,
                                               line_length, manager, True)
            if misformatted:
                misformatted_files.add(file)
        except InvalidRstErrors as errors:
            reporter.error(str(errors))
            context.exit(1)
        context.exit(0)

    cache = FileCache(context)
    if len(files) < 2:
        for file in files:
            misformatted, error_count = _format_file(
                check,
                Path(file),
                file_type,
                include_txt,
                line_length,
                mode,
                docstring_trailing_line,
                raw_output,
                None,
            )
            if misformatted:
                misformatted_files.add(file)

    else:
        # This code is heavily based on that of psf/black
        # see here for license: https://github.com/psf/black/blob/master/LICENSE
        executor = None
        loop = asyncio.get_event_loop()
        worker_count = os.cpu_count()
        if sys.platform == "win32":  # pragma: no cover
            # Work around https://bugs.python.org/issue26903
            worker_count = min(worker_count, 61)
        try:
            executor = ProcessPoolExecutor(max_workers=worker_count)
        except (ImportError, OSError):  # pragma: no cover
            # we arrive here if the underlying system does not support multi-processing
            # like in AWS Lambda or Termux, in which case we gracefully fallback to
            # a ThreadPollExecutor with just a single worker (more workers would not do us
            # any good due to the Global Interpreter Lock)
            executor = ThreadPoolExecutor(max_workers=1)
        try:
            misformatted_files, error_count = loop.run_until_complete(
                _run_formatter(
                    check,
                    file_type,
                    files,
                    include_txt,
                    docstring_trailing_line,
                    mode,
                    raw_output,
                    cache,
                    loop,
                    executor,
                ))
        finally:
            shutdown(loop)
            if executor is not None:
                executor.shutdown()
    if misformatted_files and not raw_output:
        if check:
            reporter.print(
                f"{len(misformatted_files)} out of {plural('file', len(files))} could be reformatted."
            )
        else:
            reporter.print(
                f"{len(misformatted_files)} out of {plural('file', len(files))} were reformatted."
            )
    elif not raw_output:
        reporter.print(f"{plural('file', len(files))} were checked.")
    if error_count > 0:
        reporter.print(
            f"Done, but {plural('error', error_count)} occurred ❌💥❌")
    elif not raw_output:
        reporter.print("Done! 🎉")
    if (check and misformatted_files) or error_count:
        context.exit(1)
    context.exit(0)