Effortless REST with Flask, Part 1

Image for post
Image for post

As an industry leader, American Tire Distributors (ATD), delivers tires and wheels to thousands of customers a day and meeting our customers’ expectations requires us to be as efficient with our time as possible. Meet David, a principal software engineer, who’s a member of our analytics team. During a presentation at PyData Charlotte, he walked users through building a production-ready Python Flask API from start to finish, in under 60-minutes. Now that’s being efficient with your time.

In this three-part blog series, David shares insights from his presentation and how our analytics team creates production-ready applications for ATD to help our customers thrive and drive into the future. In Part 1, he’ll walk you through building a simple API with Flask and prepare you to make a full-featured API ready to deliver results. Let’s get started.

A new way to look at an API

As a technologist, I’m here to show you a new way to look at an API and explain why it’s important to understand how they work. More than likely, ML models in production are being served over an API. Whether it’s an internal or external source, these models require some medium (like a quarriable API) to feed it with information. That’s why it’s important when taking a model from conception to implementation in production, to understand an API and how information is being quarried.

Qualities of a production-ready API

You could have an API up and running quickly with one file called “hello_world.py” which may be exhilarating for the C developer that wrote thousands of lines of code to get a simple web server built. However, those scenarios may not be practical when you’re exploring to develop production-ready software. A production-ready API is more than just simple routes attached to logic. Let’s take a look at some qualities and questions necessary for a production-ready API are:

  1. Authentication — How can I identify users and grant them access to the API?
  2. Authorization — How can I properly give permissions within the API?
  3. Database Connections and Management — How do I get underlying data to serve via the API?
  4. Documentation — How do I tell my API clients how to use the API?
  5. Proper Logging — What feedback should my API give me about its current processes?

The simple “hello_world” example wouldn’t be able to handle a production environment due to lacking some of the qualities listed above. To build our production-ready API that possesses these qualities, we’ll be using Python and Flask. You can access the code from the git repo here.

Building a Simple API

from flask import Flaskapp = Flask(__name__)@app.route('/')def hello_world():return 'Hello, World!

While the above is not production-ready, it does define a simple route. You may ask yourself, how is this possible with one file and a minimal amount of code? The answer is dependencies and packages. If you’ve ever worked with Python, you’re probably familiar with the very rich Python package index called PyPi. If you haven’t worked with Python, this package includes opensource code that others have already written, vetted and tested that’s free to use. The same goes for Flask, which is a small package that allows you to build an API with minimal overhead very quickly. This is what Flask calls, a “microframework,” because it’s just a small piece when building an API with production-ready qualities. The remaining pieces rely on other community-written and respected Flask plugins to fill in the gaps. This is helpful when applying specific features to an app that are needed instead of forcing a heavy framework, which includes features that aren’t required for production.

Packages and plugins

Let’s take a look at some recommended and well-respected Flask plugins:

  1. Flask-RestX (Previously Flask-RestPlus) — Helps to build an organized REST API quickly with documentation
  2. Flask SQLAlchemy — The ORM layer to help fetch data from a relational database
  3. Flask Praetorian — A JWT based API authentication and authorization library

Now that we’ve identified the plugins, they need to be installed. It’s standard to include a requirements.txt file in your project to declare all the packages and plugins that your Python app will use. You’ll find the requirements.txt file at the top level of the repo, if you’re following along with the provided code. To install these, you’ll need to run the following code:

pip install -r requiremnts.txt

Main Flask app

Now that these are installed, let’s take a look at our simple API server in chap-1/app/__init__.py .

"Main Flask App"# pylint: disable=import-outside-toplevelfrom logging import Loggerfrom typing import Listfrom flask import Flask, jsonify, abort, request# Application Factory# https://flask.palletsprojects.com/en/1.1.x/patterns/appfactories/def create_app(config_name: str) -> Flask:"""Create the Flask applicationArgs:config_name (str): Config name mapping to Config ClassReturns:[Flask]: Flask Application"""from app.config import config_by_name# Create the appapp = Flask(__name__)# Log the current config name being used and setup app with the configapp.logger.debug(f"CONFIG NAME: {config_name}")config = config_by_name[config_name]app.config.from_object(config)@app.route("/")def hello_world() -> str:  # pylint: disable=unused-variablereturn "Hello World!"return app

Although similar to the previous “hello world” example, this includes a few key features that’ll help extend our app later. The first difference is that we’ve wrapped our app declaration in a function called create_app. This is called an application factory. The main benefit of an application factory is that we can create multiple instances of our application, which helps us extend our app for later complexities. For example, we can pass different configurations to our application, such as a testing configuration meant for unit testing with a test database.

Configuring your Flask app

Speaking of configuration, how do we go about setting them? Configs for this example are placed in the chap-1/app.config.py.

""" Provide the capability to configure the app based on the target environment. """# Flask configs: https://flask.palletsprojects.com/en/1.1.x/config/# Flask-SqlALchemy configs: https://flask-sqlalchemy.palletsprojects.com/en/2.x/config/import osfrom typing import DictBASEDIR = os.path.abspath(os.path.dirname(__file__))class Config:""" Base config. """DEBUG: bool = FalseSECRET_KEY: str = "top secret"class TestingConfig(Config):""" Testing config. """# Override defaults from parent.#DEBUG: bool = TrueSECRET_KEY: str = "my_precious_secret_key"#  Exceptions are propagated rather than handled by the the app's error handlers.TESTING: bool = Trueclass DevelopmentConfig(Config):""" A config to be used for development, use mocks so you don't need a DB. """# Override defaults from parent.#DEBUG: bool = TrueSECRET_KEY: str = os.getenv("SECRET_KEY", "my_precious_development_secret_key")class ProductionConfig(Config):""" Production config. """# Inherits defaults from parent.SECRET_KEY: str = os.getenv("SECRET_KEY", "dc89aa6c-93e7-474d-a55a-b2113b25fc16")config_by_name = dict(  # pylint: disable=invalid-namedev=DevelopmentConfig, test=TestingConfig, prod=ProductionConfig)

This type of dynamic configuration enables you to easily switch between how the application acts based on different environments. For now, we can see that even in the TestingConfig class, we’ve defined a TESTING configuration parameter to the app that makes the application run differently. We can later add to this TestingConfig class to our database connection strings for our unit testing database, as well as our production database connection strings to our ProductionConfig class.

Starting your Flask app

With your code written out, it’s time to start the app! When starting the application, this can be done in one of two ways.

  1. Run the application using the `flask` CLI command
  2. Run the application using a web server such as gunicorn

Using the Flask CLI command is recommended. Why? This way will allow you to easily use the factory pattern from the CLI along with gaining a development flask server that hot-reloads on any changes. To run our flask server, cd in the chap-1 directory and run the following code:

export FLASK_ENV=development &&export FLASK_APP="app:create_app('dev')" &&flask run --host=127.0.0.1

The Flask CLI command depends on the environment variables set on your host environment. The FLASK_ENV configures the Flask server to hot-reload and provides extra development features while the FLASK_APP variables tell the Flask CLI what Flask app to run. In this case, we’ve said we want to run the application in the app package, which defaults to use the __init__.py file, with the dev passed into the app factory.

Once the server is running, you can go to http://127.0.0.1:5000/ and see a wonderful “Hello World!” message.

Image for post
Image for post

Flask CLI will not be able to be used when starting your API server in production. When running in production, the Flask app will need a Web Server Gateway Interface (WSGI) server to sit behind. A WSGI server will interact with our Flask application through a WSGI contract. This is where Gunicorn comes in.

Image for post
Image for post

Gunicorn is a popular WSGI server that can manage large request loads, multiple worker threads and other vital configurations when the app is moved into production. To start the Gunicorn web server and serve the Flask app, run the following code:

gunicorn --bind 127.0.0.1:5000 --workers 2 "app:create_app('prod')"

This command is telling Gunicorn to run our application with two worker threads at our loopback address of 127.0.0.1 and port 5000. Notice that we’re using the prod configuration for the application. Simple enough! This is the type of command we would use when we start our web server for production.

Say hello to Invoke

Instead of typing these long commands to start the server when developing a production-ready application, you can use Invoke to build pre-defined tasks with easy configuration options. These options will allow you to efficiently start your server, saving you lots of time. In the git repo, you’ll see a tasks.py file that includes the definitions of each command. This easily allows us to start the server by just entering:

Invoke start

And to start the WSGI server, just enter:

Invoke start -w

What a great start!

Congratulations on constructing your simple API with Flask! We look forward to having you join us for Effortless REST with Flask, Part 2, as we’ll be making our API more useful and secure by connecting a data source and authenticating users. With the addition of these two key elements, you’re a few steps closer to building a production-ready Python Flask API. See you next time!

Who we are as people is who we are as a company. We share the same values in the way we work with each other, our partners and our customers. We are ATD.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store