How this Post Works
- This post is part of a series which dissects my method for creating a REST API using Python and Flask.
- Each post is written under the assumption that the reader has read and understood all previous posts in the series.
- I will attempt to explain every choice made along the way. If you feel I've left something out please mention it in a comment below.
- Each section explains why and how to use a specific technique or technology. Feel free to skip sections you're already comfortable with.
- The source code generated for this series can be found on GitHub.
dbanty / python-rest
A sample project showing a basic Flask REST API
Hello! I created this repo to track a blog post series about Flask, but I never finished it and never will. I actually stopped using Flask in favor of FastAPI and recommend you do the same for a truly wonderful experience in creating Python REST APIs.
Why CI?
There is little reason today to not have continuous integration tests. There are tools that will do it for free if you're developing an open source project, and plenty of options which don't cost too much for closed source. The value you get out of constant feedback on your project cannot be overstated. If you're not already using some form of continuous integration for your existing projects, stop reading this and go do that. When starting a new project, I highly recommend adding CI as one of the first steps to get constant, early feedback. This will save you a lot of time fixing issues later on.
You can do basically anything with a CI server, but here are the things most important for me:
- Run all the tests- this saves you a lot of time because you never have to run full regression tests manually. It also makes sure you don't release something you forgot to test.
- Code coverage- simply running the tests isn't enough, what if you forgot to write a test? You may use test driven development and have 100% code coverage, but another developer on your team might not. There are tools that check to see how much of your code is covered by your tests so you can make sure you aren't committing untested code.
- Style checks- especially important when you have multiple developers working on a project. Having consistent style saves you a lot of time by increasing readability. Having an automatic style checker means you don't have to worry about doing it manually during code reviews.
- Static analysis- catch bugs you may have missed with your tests. Your options here will vary a lot based on what language you're using.
- Vulnerability scans- integrating security into your build pipeline is crucial. One of the simplest ways you can increase your security is to check for known vulnerabilities in any open source packages you're using.
Recommended Tools
There are too many options for CI to list them all, but here are the ones I feel comfortable recommending:
- TravisCI for GitHub- It's free for open source projects and is used everywhere. You're probably using a FOSS project right now which is being checked with TravisCI. My example project for this series will use TravisCI, at least to start.
- CircleCI for Bitbucket- very similar to TravisCI but you can use it with Bitbucket Cloud. You can also use the built in Bitbucket Pipelines, though that costs money
- CodeBuild on AWS- This one has a free tier but costs money after a certain amount of usage each month. Probably a good bet if you're using AWS CodeCommit as your repo (though I've never met anyone who is). This project will move to CodeBuild once we're ready to try Continuous Deployment.
- GitLab has their own built in CI/CD tool. I've never used it but it has a free tier.
- Jenkins- the classic example of a CI server. It's open source and has a bunch of plugins, but you really have to do everything yourself, including hosting it somewhere.
The Example
Alright, now that I've covered CI broadly, it's time to actually add it to our project! First, let's select the tools we're going to use and test them all out locally.
pytest
We've already used pytest in an earlier post, but as a quick refresher, to test the entire project via the command line you type poetry run pytest
.
Black
My style checker of choice is Black, chiefly because it is always the same across every project that uses it. If you see a project that uses Black, you know exactly what to expect, no need to parse some config file or style document to learn the rules. In addition, Black will format your code for you, so there's no need to go finding and fixing individual style complaints manually, that's time consuming, frustrating, and a waste of your brain power.
Now that I'm done selling it, here's how you use it:
- Install (Note that black is still beta):
poetry add -D black --allow-prereleases
At the time of writing, poetry won't install the latest version. Update it by changing the version in
pyproject.toml
to >19, then runpoetry update
- Check your code:
poetry run black . --check
- Fix any issues:
poetry run black .
mypy
As I mentioned in a previous post, I'm a huge fan of type annotations. Every since I started programming in Rust, I've realized how many logic errors you can catch by using a type system. Because Python doesn't enforce any types that you annotate, you need another tool to statically check for issues, and mypy is my tool of choice.
Here's what to do:
- Install:
poetry add -D mypy
- Check your code:
poetry run mypy python_rest
This won't find anything right now, but if you want to see what it looks like when it fails, add this to a file in the module and run mypy:
def return_int() -> int:
return 0
def use_string(a: str):
print(a)
use_string(return_int())
Note that mypy (and PyCharm) only catch that this is an error because of the type annotations (since printing an int is perfectly valid code).
Safety
Earlier I told you that checking for known vulnerabilities in your dependencies is an easy way to improve your security. Now I'll show you how easy it is:
poetry add -D safety
poetry run safety check
- Hopefully none of the packages
TravisCI
We're going to set up TravisCI to check our project on every commit, but first we need to tell TravisCI how to do it. This is done in most tools via a config file in the root of the project. In this case, it should be named .travis.yml
and look like this:
dist: "bionic"
language: "python"
python:
- "3.7"
install:
- pip install poetry
- poetry install
script:
- poetry run safety check
- poetry run black . --check
- poetry run mypy python_rest
- poetry run pytest
Note the order I put the checks in, I try and put the quickest tests first so that if they fail we don't have to wait all the way through the longer checks to get that result (specifically, the unit tests will take the longest and should be at the end). This matters more if you're using a pay-per-minute CI tool, which many of the closed source offerings are.
Now you need to go to travis-ci.org and sign in with your GitHub account. Enable builds for this project.
Sprucing up the README
A nice touch to any project is to put a quick look into your CI process in your readme. The pretty way to do this is via badges, let's add a few.
[![Build Status](https://travis-ci.org/dbanty/python-rest.svg?branch=master)](https://travis-ci.org/dbanty/python-rest)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black)
[![Checked with mypy](http://www.mypy-lang.org/static/mypy_badge.svg)](http://mypy-lang.org/)
Run!
Push these changes to GitHub, you'll be able to see your build status right against your commit, in your TravisCI dashboard, and even in your readme badge!
Top comments (2)
Good.
But at the end you have shifted the article to the CI part.
Where is the Flask REST API part ?
I was waiting to see how do you deal with the tests for the API (emulating the client-server communication).
I suggest this library for that use case.
Yeah I know, the problem with the scope of what I’m trying to cover in this series is that it’s hard to put things in bite sized chunks while keeping the larger perspective.
I actually write the first basic test in the previous post using pytest-flask and will continue to expand tests as I add features to the project. Stay tuned for future posts :) and thanks for your comment!