bellybutton is a customizable, easy-to-configure linting engine for Python.
What is this good for?
Tools like pylint and flake8 provide, out-of-the-box, a wide variety of rules for enforcing Python best practices, ensuring PEP-8 compliance, and avoiding frequent sources of bugs. However, many projects have project-specific candidates for static analysis, such as internal style guides, areas of deprecated functionality, or common sources of error. This is especially true of those projects with many contributors or with large or legacy codebases.
bellybutton allows custom linting rules to be specified on a per-project basis and detected as part of your normal build, test and deployment process and, further, makes specifying these rules highly accessible, greatly lowering the cost of adoption.
bellybutton a try if:
- You find yourself making the same PR comments over and over again
- You need a means of gradually deprecating legacy functionality
- You’re looking to build a self-enforcing style guide
- Your project needs to onboard new or junior developers more quickly and effectively
- You have Python nitpicks that go beyond what standard linting tools enforce
Installation & getting started
bellybutton can be installed via:
pip install bellybutton
Once installed, running
in your project’s root directory will create a
.bellybutton.yml configuration file with an example rule for you to begin adapting.
bellybutton will also try to provide additional rule settings based on the directory structure of your project.
Once you have configured
bellybutton for your project, running
will lint the project against the rules specified in your
.bellybutton.yml. Additionally, running
bellybutton lint --modified-only
will, if using git, only lint those files that differ from
bellybutton supply patterns that should be caught and cause linting to fail. Rules as specified in your
.bellybutton.yml configuration must consist of:
- A description
description, expressing the meaning of the rule
- An expression
expr, specifying the pattern to be caught – either as an astpath expression or as a regular expression (
Additionally, the key used for the rule within the
rules mapping serves as its name.
Rules may also consist of:
settingsthat specify on which files the rule is to be enforced, as well as whether it can be ignored via a
# bb: ignorecomment
- An example
exampleof Python code that would be matched by the rule
- A counter-example
insteadof an alternative piece of code, for guiding the developer in fixing their linting error.
instead clauses are checked at run-time to ensure that they respectively are and are not matched by the rule’s
As an example, a rule to lint for a deprecated function call using an astpath expression might look like:
DeprecatedFnCall: description: `deprecated_fn` will be deprecated in v9.1.2. Please use `new_fn` instead. expr: //Call[func/Name/@id='deprecated_fn'] example: "deprecated_fn(*values)" instead: "new_fn(values)"
!settings nodes specify:
includedpaths on which rules are to be run, using glob notation
excludedpaths on which rules are not to be run (even when matching the
- A boolean
allow_ignorewhich determines whether rules can be ignored, providing the line matching the rule has a
# bb: ignorecomment.
Additionally, at the root level of
default_settings setting may be specified which will be used by rules without explicit settings. Each rule must either have a
settings parameter or be able to fall back on the
As an example, a
!settings node to lint only a specific module might look like:
my_module_settings: !settings included: - ~+/my_package/my_module.py excluded:  allow_ignore: no
bellybutton is in an alpha release and, as such, is missing some key features, documentation, and full test coverage. Further,
bellybutton is not optimized for performance on extremely large codebases and may contain breaking bugs. Please report any bugs encountered.
!verbalexpression nodes are not yet implemented