Skip to main content

Blank Line Enforcement for Python

Project description

Blanken

Blank Line Enforcement for Python

Overview

Blanken is a Python code auto-formatter that enforces separation of logical code blocks via proper usage of blank lines.

It uses indentation changes and keywords as the primary way to know where logical blocks start or end, and inserts blank lines to separate them if they are missing.

Usage

Run on one or more files to insert blank lines where required:

blanken path/to/file.py

Use from Python:

from blanken import enforce

enforce(["path/to/file.py"])

Pre-commit

Add the hook to your .pre-commit-config.yaml:

repos:
- repo: https://github.com/csala/blanken
  rev: v0.1.0
  hooks:
  - id: blanken

This hook will auto-format the files and fail if any file has been modified by blanken.

Philosophy

The idea behind Blanken is to guarantee that source code is easy to read and understand at first glance with as little mental effort as possible. And we, the blanken authors, believe that blank lines play a super important role in this "first glance" understanding: by using them to properly separate logically unrelated blocks of code, we help the brain quickly understand the context in which the code that is being read operates while keeping it isolated from other unrelated blocks.

Identifying which lines of code are related or unrelated with simple rules that do not leverage semantic understanding of the code is really hard. For this reason, blanken only enforces a few simple rules based on keywords and indented blocks: We understand that when indentation is reduced, the lines that follow are logically unrelated to the previous ones, with the exception of some special continuation blocks like else or elif, where the lines that follow may need to be kept in consideration to fully understand the previous ones.

Enforced rules

Dedent by 2+ levels requires one blank line

If we find a line that is has 2 or more indentation levels than the previous one, we know for sure that the lines that follow belong to a new logical block, because it cannot be a continuation block.

Therefore, we enforce a blank line.

Bad:

if outer():
    if inner():
        do_inner()
do_next()

Corrected:

if outer():
    if inner():
        do_inner()

do_next()

Dedent by 1 level requires one blank line if no continuation keyword is found.

If we find a line that has 1 indentation level less than the previous one, we consider that it is unrelated to the previous one if it does not start with any of the following continuation keywords:

  • else
  • elif
  • except
  • finally

Good:

if something:
    do_something()
elif something_else:
    do_something_else()
else:
    do_another_thing()

try:
    something_dangerous()
except SomeException:
    log_the_error()
finally:
    clean_things_up()

Bad:

for item in items:
    handle(item)
while some_condition:
    run_some_logic()
log_done()

Corrected:

for item in items:
    handle(item)

while some_condition:
    run_some_logic()

log_done()

Dedent by 1 level requires one blank line if block is too long

If we find a line that has 1 indentation level less than the previous one and is followed by one of the continuation keywords listed above, we do not require a blank line if the block is short.

If the indented block is too long or complex, we understand that the block will need to be read and understood on its own, and that it is better to separate it visually from the one that follows, even if the next one starts with one of the continuation lines.

In particular, if the indented block has more than 3 top-level statements, we require a blank line.

Bad:

if condition:
    one()
    two()
    three()
    four()
else:
    five()

Corrected:

if condition:
    one()
    two()
    three()
    four()

else:
    five()

The example above seems probably simple, but consider the next one. Bear in mind that only top-level statements are counted:

Bad:

if condition:
    one()
    if two:
        nested_one()
        nested_two()
    else:
        nested_three()

    for item in three:
        nested_four()
        for nested_five:
            nested_six()
            nested_seven()

    four()
else:
    five()

In a situation like the one above, one could easily be tricked into missing the four() statement, since visually it falls closer than the five() call that is unrelated.

So we enforce a blank line:

Corrected:

if condition:
    one()
    if two:
        nested_one()
        nested_two()
    else:
        nested_three()

    for item in three:
        nested_four()
        for nested_five:
            nested_six()
            nested_seven()

    four()

else:
    five()

Development Roadmap

  • Standalone script to run on individual files
  • Installable package and cli tool to run on individual files
  • Pre-commit hook
  • Run recursively on folders
  • Separate validation from file formatting
  • Add discovery CLI options (such as exclude or include)
  • Read options from pyproject
  • Add formatting options

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

blanken-0.1.1.tar.gz (23.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

blanken-0.1.1-py3-none-any.whl (7.0 kB view details)

Uploaded Python 3

File details

Details for the file blanken-0.1.1.tar.gz.

File metadata

  • Download URL: blanken-0.1.1.tar.gz
  • Upload date:
  • Size: 23.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.7

File hashes

Hashes for blanken-0.1.1.tar.gz
Algorithm Hash digest
SHA256 db084af33c5105854e5646e721906198e3f40f02f3000ba46a56ed9a007ca3dc
MD5 a0f1ca8a2bf7e8cc5d7461f84b8f6c45
BLAKE2b-256 99f94bc88657c6b867b75b28928c983e86ffdaa0541f086948b749493aac398b

See more details on using hashes here.

File details

Details for the file blanken-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: blanken-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 7.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.7

File hashes

Hashes for blanken-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 81ef8e06ee8a5ff367b54205d064ff1efb0561bff3477cdc6f8a89b140c87b94
MD5 c821f751ab7cf3ac4efe9d72563f9c18
BLAKE2b-256 8ef1a7b07b72c4696e19483d05f6743469b28ab4ee04865bdbab9be18af0d038

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page