rules_python
rules_python copied to clipboard
Python code formatting rule
🚀 feature request
Relevant Rules
This feature request is for a new rule.
Description
A clear and concise description of the problem or missing capability...I'm looking for a rule I can use to format python code and enforce formatting (likely via an aspect).
It'd be great if this rule was configurable and worked with (at least):
There's a similar rule in rules_rust for rustfmt where formatting can be enforced via an aspect and applied via a runtime rule which I've found to be very impactful. A python rule would be fantastic.
Describe alternatives you've considered
Have you considered any alternative solutions or workarounds?There's likely an existing rule in someone's personal repository for this but I'm looking for something that can be promoted to live in rules_python directly. Rules in rules_rust have more visibility, particularly if they're communicated in the release notes. I'm willing to try out a 3rd party solution but only if there's a path to up-streaming that solution.
I was able to knock out a prototype of flake8 using this aspects model, the aspects docs and the rules_rust examples. Not ideal at all but workable. https://github.com/arrdem/source/blob/trunk/tools/flake8/flake8.bzl
Unfortunately the aspects model really only works for incremental verification of formatting, there doesn't seem to be a an equivalent to pants --changed-since=origin/main | xargs ./pants fmt, so an external formatting script is likely the best we get. As such, I'm not sure that making lint errors build breaking in development is a super great idea, but you can definitely do it with an aspect like this.
This issue has been automatically marked as stale because it has not had any activity for 180 days. It will be closed if no further activity occurs in 30 days. Collaborators can add an assignee to keep this open indefinitely. Thanks for your contributions to rules_python!
This functionality is still very much desired.
At our company I built a macro that generates a python unit test which runs black --check, pyflakes, and pylint, and then declares a py_test target for it. In hindsight, I probably should have just had it run flake8 with plugins for the others, but live and learn. For a simpler approach, you can have a macro run a genrule to format the source file, and then use a diff_test (from skylib) to check the result.
At any rate, I think that approach (having individual test targets) is generally more idiomatic than having one target which globs up all of your sources and tests them all at once. On the other hand, then you need a way to enforce that those rules are applied to new source files.
This issue has been automatically marked as stale because it has not had any activity for 180 days. It will be closed if no further activity occurs in 30 days. Collaborators can add an assignee to keep this open indefinitely. Thanks for your contributions to rules_python!
This is still desirable
This issue has been automatically marked as stale because it has not had any activity for 180 days. It will be closed if no further activity occurs in 30 days. Collaborators can add an assignee to keep this open indefinitely. Thanks for your contributions to rules_python!
I would be interested in this functionality as well.
Maybe we'll have bandwidth to open-source our rule set at some point, but in the mean time, this is really not that hard. You can use a macro with write_file to write a little script, for example
import unittest
from black import main as _black_main
class TestFormat(unittest.TestCase):
def test_black(self):
_black_main(
[
"--color",
"--check",{}
r"{}",
]
)
if __name__ == "__main__":
unittest.main(buffer=True, verbosity=0)
subbing in the --config= if applicable and the list of paths to check, and then make a py_test out of that. If you want to be able to pass in py_{library,test,binary} targets in addition to passing the sources directly then you need a rule rather than write_file for the first bit but it's still not hard.
We have our internal rule as well, but looking at the rustfmt source I see that it is much more solid. Our current approach is a custom _test rule for python formatting that is running both, black and isort at the same time.
Ours also runs pyflakes and pylint, and also imports the module as a basic sanity check of the dependency declarations. Maybe a bit much to dump into a single test target? But on the other hand we have a lot of python files and I run out of inodes from all of the runfiles symlink forests if we break them up into too many separate targets...
I also use absltest.main() rather than unittest.main() as a lightweight way to get those individual check results to get properly wrapped up in junit xml for cleaner output.
+1
The approach of wrapping additional tests around declared targets is what apple_rules_lint facilitates. We've integrated this into contrib_rules_jvm to provide spotbugs, checkstyle and PMD. I would be happy to help land something similar here.
https://github.com/aspect-build/bazel-super-formatter may be interesting to anyone following this thread, it uses Bazel to create a formatter that runs Black on Python code, and also formats most other languages using their idiomatic formatter tool. It doesn't require any changes in BUILD files.
This issue has been automatically marked as stale because it has not had any activity for 180 days. It will be closed if no further activity occurs in 30 days. Collaborators can add an assignee to keep this open indefinitely. Thanks for your contributions to rules_python!
Update: aspect-build/rules_lint uses super fast Ruff for formatting.