How to automate code formatting for Python projects with GitHub Actions - A study
September 10, 2020
This is a beginner-friendly 💯 post that will tell you how easy it will become to conform to Python coding styles using autoyapf GitHub Action in your Python projects!
Introduction
To begin with - let's discuss first what are GitHub Actions and what magic they bring a 😃?
GitHub Actions
GitHub Actions is that piece of magic which enables you to automate, customize, and execute your software development workflows right in your repository.
But hey? What are words without actions? And so what are actions without workflows? Yes, you got it right! For GitHub Actions to work you need to use Workflows but what are Workflows? A workflow is a configurable automated process made up of one or more jobs. Now, these jobs can be like the accounting of the trigger for the GitHub action to work - like push on the main branch.
Workflow files use YAML syntax and must have either a .yml or .yaml file extension.
For GitHub Actions to work you must store workflow files in the .github/workflows
directory of your repository.
Brief Introduction to building a GitHub Action
Note: In order to use an existing GitHub action all you require is just a Workflow YAML file in
.github/workflows
. The files mentioned in this part of the blog are required when you start building your own GitHub Actions 😄
In order to make almost any GitHub Action - you require at least these files:
Dockerfile — GitHub actions are of three types - Docker actions, Javascript actions and composite run steps action. In our case- we are going to use Docker Action and so Dockerfile is used to define the container environment that runs the action.
action.yml — This will define inputs, outputs, Entrypoint for our action, and other details like name of the action, description, author name, etc.
script — An action might require a script to perform a task, in our case we just need an Entrypoint script named as
entrypoint.sh
README.md — This is optional, but if we want to publish our action on the Marketplace we need it. README.md file is useful to provide examples that are needed for the action to run without any hassles.
This is the basic skeleton of how the GitHub actions are built. If you want to start with making your own first action, check out this official Hello World tutorial
Let's move on to the main part of preparing and setting up the workflow for the autoyapf GitHub Action we are going to use.
What is autoyapf?
autoyapf is a GitHub action for yapf, an open-source tool that automatically formats Python code to conform to the PEP 8 style guide. It is in essence, the algorithm that takes the code and reformats it to the best formatting that conforms to the style guide, even if the original code didn't violate the style guide.
This action is designed to be used in conjunction with the 'push' trigger. In simple words - everytime the maintainer pushes the code from their own playground or merges a Pull Request from a contributor it will be formatted automatically with the PEP 8 Style guidelines using the yapf tool. This action is a win-win situation for both contributors and maintainers of projects involving Python!
Choosing the Project
The first step is always choosing the repository where you will be using the GitHub action. Since the GitHub action is made for python projects, I am going to use an open-source Python project that I created recently called sedpy
Preparing the Workflow
- We'll go to our repository and click on the Actions tab. That will then reflect an option like set up a workflow yourself. Click it.
This will create a YAML file in the path: .github/workflows/main.yml
You can rename the file to whatever you prefer like, in this case, I will give it the name autoyapf.yml instead of main.yml.
Now, let's dive in the autoyapf.yml file:
name: Formatting python codeon:push:paths:- '**.py'
What we have done until here is:
- Given a name to the workflow:
Formatting python code
- Defined what event will cause the workflow to trigger. In this case, it is triggered when the python file is pushed in the repository.
Next step is to define the jobs, further, we added:
jobs:autoyapf:runs-on: ubuntu-lateststeps:- uses: actions/checkout@masterwith:ref: ${{ github.head_ref }}- name: autoyapfid: autoyapfuses: mritunjaysharma394/autoyapf@v2with:args: --style pep8 --recursive --in-place .- name: Check for modified filesid: git-checkrun: echo ::set-output name=modified::$(if git diff-index --quiet HEAD --; then echo "false"; else echo "true"; fi)- name: Push changesif: steps.git-check.outputs.modified == 'true'run: |git config --global user.name 'Mritunjay Sharma'git config --global user.email 'mritunjaysharma394@users.noreply.github.com'git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}git commit -am "Automated autoyapf fixes"git push
Here's a brief detail about almost everything the above snippet does:
- The jobs that the workflow has to perform will run on the GitHub hosted runner which is
ubuntu-latest
in our case.
jobs:autoyapf:runs-on: ubuntu-latest
- We define the
steps
of jobs next.- The first step checks out the repository where we want to workflow to execute. Since I am using it inside sedpy, it will check out this repository's
master
branch
- The first step checks out the repository where we want to workflow to execute. Since I am using it inside sedpy, it will check out this repository's
steps:- uses: actions/checkout@masterwith:ref: ${{ github.head_ref }}
- In the next step - we checked out the GitHub action
autoyapf which will perform all the formatting using
yapf
tool with the arguments supplied as:args: --style pep8 --recursive --in-place .
- name: autoyapfid: autoyapfuses: mritunjaysharma394/autoyapf@v2with:args: --style pep8 --recursive --in-place .
- This step checks if the above step has made any changes in the code. If it has made any changes then the next step will push the changes to the
sedpy
repository.
- name: Check for modified filesid: git-checkrun: echo ::set-output name=modified::$(if git diff-index --quiet HEAD --; then echo "false"; else echo "true"; fi)
- This will check the condition as detailed above and we will push the changes using run utility that makes us use terminal-like commands in the
.yml
files.
- name: Push changesif: steps.git-check.outputs.modified == 'true'run: |git config --global user.name 'Mritunjay Sharma'git config --global user.email 'mritunjaysharma394@users.noreply.github.com'git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}git commit -am "Automated autoyapf fixes"git push
Woohoo! 💃 We have completed our workflow autoyapf.yml
and the complete file looks something like this:
name: Formatting python codeon:push:paths:- '**.py'jobs:autoyapf:runs-on: ubuntu-lateststeps:- uses: actions/checkout@masterwith:ref: ${{ github.head_ref }}- name: autoyapfid: autoyapfuses: mritunjaysharma394/autoyapf@v2with:args: --style pep8 --recursive --in-place .- name: Check for modified filesid: git-checkrun: echo ::set-output name=modified::$(if git diff-index --quiet HEAD --; then echo "false"; else echo "true"; fi)- name: Push changesif: steps.git-check.outputs.modified == 'true'run: |git config --global user.name 'Mritunjay Sharma'git config --global user.email 'mritunjaysharma394@users.noreply.github.com'git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}git commit -am "Automated autoyapf fixes"git push