Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Python interface to Black #1449

Closed
Dreamsorcerer opened this issue May 22, 2020 · 10 comments
Closed

Python interface to Black #1449

Dreamsorcerer opened this issue May 22, 2020 · 10 comments
Labels
T: enhancement New feature or request

Comments

@Dreamsorcerer
Copy link

It would be nice to have a direct Python interface to black that is easy to use and documented.

e.g.

import black
black.format([...files], **kwargs)

Trying to figure out how to do this from the source code of the main() function is not very easy, and appears that the code is changing, so not very maintainable either.

@Dreamsorcerer Dreamsorcerer added the T: enhancement New feature or request label May 22, 2020
@SanketDG
Copy link
Contributor

appears that the code is changing, so not very maintainable either.

Because https://github.com/psf/black#note-this-is-a-beta-product

@Dreamsorcerer
Copy link
Author

I just mean that it's not maintainable for me to copy and paste bits of the internal code.
Providing a deliberate external API would solve this.

@hugovk
Copy link
Contributor

hugovk commented May 22, 2020

See also #779.

@cooperlees
Copy link
Collaborator

That said, we're trying hard to not break people. The git mv black.py src/black/__init__.py would have broke no API usage. When the refactoring of __init__.py takes place (@ambv is going to kick it off soon I believe) we will try to also maintain the current API but still importing the moved function etc. in __init__.py.

But I think adding a documented abstracted public API to give us more wiggle room before we go stable is probably a good idea too.

@Dreamsorcerer
Copy link
Author

So, at the moment, this is my code to run black (as part of a pre-commit hook). Current master has a get_sources() function which may reduce some of the code here (once a new release is made).

However, notice that I've changed the single file logic to check the include regex. Current black code assumes that any individual files should skip the tests, as they are explicitly given on the command line. However, here we are passing a list of files that have changed in Git, so I still need these checks to happen.

def run_black(src: Tuple[str]):
    include = re.compile(black.DEFAULT_INCLUDES)
    exclude = re.compile(black.DEFAULT_EXCLUDES)
    mode = black.FileMode(
        target_versions=(black.TargetVersion.PY37, black.TargetVersion.PY38),
        line_length=120,
        is_pyi=False,
        string_normalization=True,
    )

    report = black.Report()

    root = black.find_project_root(src)
    sources = set()
    for s in src:
        p = Path(s)
        if p.is_dir():
            sources.update(black.gen_python_files_in_dir(p, root, include, exclude, report, black.get_gitignore(root)))
        elif p.is_file() or s == "-":
            normalized_path = "/" + p.resolve().relative_to(root).as_posix()
            if include.search(normalized_path):
                sources.add(p)
    if not sources:
        return 0

    if len(sources) == 1:
        black.reformat_one(
            src=sources.pop(), fast=False, write_back=black.WriteBack.YES, mode=mode, report=report,
        )
    else:
        black.reformat_many(sources=sources, fast=False, write_back=black.WriteBack.YES, mode=mode, report=report)

    return report.return_code

@cooperlees
Copy link
Collaborator

Duplicate of #779

@cooperlees cooperlees marked this as a duplicate of #779 Jun 16, 2020
@Dreamsorcerer
Copy link
Author

That was mentioned in a previous comment, but that ticket asks for a format_str() function, rather than something that will format files. So, I think this is really an extension of that issue, rather than a duplicate.

@ichard26
Copy link
Collaborator

@Dreamsorcerer, I was the one who marked this as a duplicate. Admittedly, your issue does bring an overarching request to the table that wasn't present in the original issue. I think where I got sidetracked was with the titles. to me Python interface to Black ~= Black's public API. I've taken a closer look at the original issue, and TBH, I still consider this as a duplicate since the core team views that issue as "what's gonna be Black's public API?" And while the contents of these two issues are different in a way, I think the overarching topic is close enough.

Screenshot from 2020-06-17 10-40-54

Although, I should add a comment updating that issue for 2020.

But if you still think this issue should stay open, well, I'm open to opening it :P

@Dreamsorcerer
Copy link
Author

As long as this use case is still being considered/tracked, it's totally fine.

@Dreamsorcerer
Copy link
Author

For v20, plus now reading the config file, our code for running Black now looks like this:

class _BlackCtx:
    def __init__(self, src):
        self.default_map = {}
        self.params = {"src": src}

def _is_python(filename: str) -> bool:
    return filename.rsplit(".", 1)[-1].startswith("py")

def run_black(src: Sequence[str]) -> int:
    src = tuple(f for f in src if _is_python(f))
    ctx = _BlackCtx(src)
    black.read_pyproject_toml(ctx, None, None)

    include = ctx.default_map.get("include", black.DEFAULT_INCLUDES)
    exclude = ctx.default_map.get("exclude", black.DEFAULT_EXCLUDES)
    fast = ctx.default_map.get("fast", False)
    versions = ctx.default_map.get("target_version", set())
    mode = black.Mode(
        target_versions=tuple(black.TargetVersion[v.upper()] for v in versions),
        line_length=int(ctx.default_map.get("line_length", black.DEFAULT_LINE_LENGTH)),
        is_pyi=ctx.default_map.get("pyi", False),
        string_normalization=not ctx.default_map.get("skip_string_normalization", False),
    )

    report = black.Report(quiet=True)

    sources = black.get_sources(
        ctx=ctx,
        src=src,
        quiet=False,
        verbose=False,
        include=include,
        exclude=exclude,
        force_exclude=None,
        report=report,
    )

    if not sources:
        return 0

    if len(sources) == 1:
        black.reformat_one(src=sources.pop(), fast=fast, write_back=black.WriteBack.YES, mode=mode, report=report)
    else:
        black.reformat_many(sources=sources, fast=fast, write_back=black.WriteBack.YES, mode=mode, report=report)

    return report.return_code

Minor improvements since v19, but could still do a lot better.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T: enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

5 participants