How to make your Wagtail/Django project have good Python coding style

Last updated on May 08 2019 by Michael Yin

Introduction

In this Wagtail tutorial, I will teach you how to check coding style for your Wagtail/Django project, how to fix it and how to keep it clean and concise. After reading this article, you will get:

  1. How to check if your Wagtail project follows the PEP8 guidelines.

  2. How to auto fix your Wagtail project to make it follow PEP8 using autopep8

  3. How to organize your Wagtail project import statements using isort

  4. How to check Wagtail project coding style in CI job.

  5. Some great config files and commands which can be used in your Wagtail project directly.

Before you get started, please make sure you are in the same virtualenv as your Django/Wagtail project.

NOTE: You can find all config files in wagtail-bootstrap-blog , I would appreciate that if you could give this repo a star.

PEP8

PEP8 is a document that provides guidelines and best practices on how to write Python code.

If your Python code is following the guidelines of PEP8, then your Python code would be easy to read and maintain.

Here we use pycodestyle (formerly called pep8) to let it helps us check our Python code.

pycodestyle is a tool to check your Python code against some of the style conventions in PEP 8.

NOTE: pycodestyle might give you different output in different versions, so please be careful to make pycodestyle version number consistent in your project.

Let's first create a test python file test.py, as you can see, this file has bad coding style and not easy to read.

import math, sys;

def example1():
    ####This is a long comment. This should be wrapped to fit within 72 characters.
    some_tuple=(   1,2, 3,'a'  );
    some_variable={'long':'Long code lines should be wrapped within 79 characters.',
    'other':[math.pi, 100,200,300,9876543210,'This is a long string that goes on'],
    'more':{'inner':'This whole logical line should be wrapped.',some_tuple:[1,
    20,300,40000,500000000,60000000000000000]}}
    return (some_tuple, some_variable)
def example2(): return {'has_key() is deprecated':True}.has_key({'f':2}.has_key(''));
class Example3(   object ):
    def __init__    ( self, bar ):
     #Comments should have a space after the hash.
     if bar : bar+=1;  bar=bar* bar   ; return bar
     else:
                    some_string = """
                       Indentation in multiline strings should not be touched.
Only actual code should be reindented.
"""
                    return (sys.path, some_string)

Now we use pycodestyle to check the above file.

$ pip install pycodestyle==2.4.0   # you can change the version number as you like

$ pycodestyle test.py
test.py:1:12: E401 multiple imports on one line
test.py:1:17: E703 statement ends with a semicolon
test.py:3:1: E302 expected 2 blank lines, found 1
test.py:4:5: E265 block comment should start with '# '
test.py:4:80: E501 line too long (83 > 79 characters)
test.py:5:15: E225 missing whitespace around operator
test.py:5:17: E201 whitespace after '('
...

pycodestyle give us some output and we can fix the Python file based on the output.

There are some other tools which can do similar jobs and here I recommend you to try flake8

flake8

Flake8 is a wrapper of pycodestyle and it also add some more useful features.

$ pip install flake8==3.6.0

$ flake8 fabfile.py
test.py:1:12: E401 multiple imports on one line
test.py:1:17: E703 statement ends with a semicolon
test.py:3:1: E302 expected 2 blank lines, found 1
test.py:4:5: E265 block comment should start with '# '
test.py:5:15: E225 missing whitespace around operator
test.py:5:17: E201 whitespace after '('
test.py:5:21: E231 missing whitespace after ','
...

Here you might see flake8 and pycodestyle print the similar output, but I recommend you to use flake8 because it provide more features compared with pycodestyle

How to config flake8 check rule

By default, the max-line of Python file is 80, If you want to change the max-line length to 120 instead of 80. You can create setup.cfg in the root of your project. setup.cfg can contain many config sections for different tools, we can put them in one single file.

Below is a sample config file.

[flake8]
ignore=E501,C901,F401
exclude = */migrations/*,node_modules,*/settings/*,

max-line-length = 120

The exclude would tell flake8 to ignore some directory and ignore would tell it to ignore some errors. For example, if you have imported some module but did not use it in your code, you will see something like this. F401, module imported but unused. Then you can add F401 to your setup.cfg to let it pass.

If you want pyflake8 to ignore some lines in python file, you can append # noqa just like this.

from .base import *  # noqa

# noqa tells pyflake8 to not check this line, this is commonly used in Wagtail settings file.

Now you can try run flake8 for your Django/Wagtail project.

# cd to the root of the project, and wagtail_tuto is the directory name we want to check here
$ flake8 wagtail_tuto

Autopep8

If you are new to Python world, you might see long output after you run flake8 command, is there something that can help you solve this?

autopep8 can save you!

$ pip install autopep8==1.4.3

$ autopep8 test.py --in-place --aggressive --aggressive

Now you can see the test.py has been fixed.

import math
import sys


def example1():

    # This is a long comment. This should be wrapped to fit within 72
    # characters.
    some_tuple = (1, 2, 3, 'a')
    some_variable = {
        'long': 'Long code lines should be wrapped within 79 characters.',
        'other': [
            math.pi,
            100,
            200,
            300,
            9876543210,
            'This is a long string that goes on'],
        'more': {
            'inner': 'This whole logical line should be wrapped.',
            some_tuple: [
                1,
                20,
                300,
                40000,
                500000000,
                60000000000000000]}}
    return (some_tuple, some_variable)


def example2(): return ('' in {'f': 2}) in {'has_key() is deprecated': True}


class Example3(object):
    def __init__(self, bar):
        # Comments should have a space after the hash.
        if bar:
            bar += 1
            bar = bar * bar
            return bar
        else:
            some_string = """
                       Indentation in multiline strings should not be touched.
Only actual code should be reindented.
"""
            return (sys.path, some_string)

You can also let autopep8 to help you fix the whole directory

autopep8 project_dir --recursive --aggressive --in-place --verbose

Please make sure you have two --aggressive in your command, the file would not be fixed if you only have one --aggressive

Troubleshoot Possible nested set for EXTRANEOUS_WHITESPACE_REGEX

Some people might see above error when run autopep8.

  1. Please make sure you have the same pycodestyle and autopep8 version number as this tutorial.

  2. After you install the package, please open a new terminal to check again. (I have met this problem and I fixed it using this way)

The last thing you need keep in mind is that autopep8 is not silverbullet and can not solve all problems in most cases.

At that time, you need to use Google and read some doc to fix it.

isort

When coding with Wagtail, most code are coming from django, wagtail, and some are coming from other 3-party packages. isort can help us better manage the import statement.

$ pip install isort
$ isort --recursive project_dir --skip project_dir/wsgi.py --skip-glob "*/migrations/*" --skip-glob "*/node_modules/*"

How to config isort rule

Edit setup.cfg

[isort]
default_section = THIRDPARTY
known_django = django
known_wagtail = wagtail
sections = FUTURE,STDLIB,DJANGO,WAGTAIL,THIRDPARTY,FIRSTPARTY,LOCALFOLDER

Makefile

Let's add Makefile so we can check our code using simple make pylint instead of long command.

pylint:
    isort --recursive project_dir --skip project_dir/wsgi.py --skip-glob "*/migrations/*" --skip-glob "*/node_modules/*"
    flake8 project_dir

Now you can check your Python code by using make pylint, which is very clean and easy to remember.

Check Python coding style in CI job

You can also check coding style in your CI job, and return error or warning to keep your project code always have good style.

Below is the travis CI config file, you can modify it as you like.

Here the travis CI would mark the job as fail if it finds some style errors in code.

language: python
python: "3.6"
cache:
  pip: true

# Use container-based infrastructure
dist: trusty
sudo: false

env:
  global:
    - DJANGO_SETTINGS_MODULE=wagtail_tuto.settings.dev

install:
  - pip install wheel
  - pip install -r requirements.txt

script:
  # Code style, missing imports, etc
  - flake8 wagtail_tuto
  - isort --check-only --diff --recursive wagtail_tuto --skip-glob "*/migrations/*"

Conclusion

In this Wagtail tutorial, I talked about how to make your Wagtail/Django project have good python coding style.

NOTE: You can find all config files in wagtail-bootstrap-blog , I would appreciate that if you could give this repo a star.

What should you go next?

  1. Try to start using above tools in your project and contact me if you still have question.

  2. I will also publish tutorials about how to make your Wagtail project front-end code has good coding style, you can Subscribe to our Newsletter to get a notification at that time.

Thanks.

Michael is a passionate Python developer from China and love writing code, articles about Django and Wagtail CMS.

Michael Yin

Full Stack Developer

Need any Help in your Project?

Sign up to our newsletter