DEV Community

Cover image for Python Flask API - Starter Kit and Project Layout
Juan Cruz Martinez
Juan Cruz Martinez

Posted on • Originally published at livecodestream.dev on

Python Flask API - Starter Kit and Project Layout

APIs changed the way we build applications, there are countless examples of APIs in the world, and many ways to structure or set up your APIs. Today we will discuss how I use Python and Flask to build and document REST APIs that scale to every need.

As usual, I’m providing sample applications, for this case a starter kit for everyone to use and build upon, here is the link to the final code we will review today.

Before we start with code…

Let’s first discuss the project dependencies, why each of them is necessary and how it can benefit our project.

  • flask:Flask is a micro web framework which has a minimal footprint compared to others like django, and allows us to select which components and modules we want to integrate with it. Flask is highly reliable and performant.
  • flasgger:It’s a module that helps us integrate swagger docs to a flask API.
  • flask-marshmallow:Object serializer, ideal for parsing and dumping JSON data in and out of our API.
  • apispec:Required for the integration between marshmallow and flasgger.

Project Layout

We will start discussing how the project layout looks like by taking a look into the folder structure:

project/
    api/
        model/
            __init__.py
            welcome.py
        route/
            home.py
        schema/
            __init__.py
            welcome.py

    test/
        route/
            __init__.py
            test_home.py
        __init.py

    .gitignore
    app.py
    Pipfile
    Pipfile.lock

I think that the folder structure is self-explanatory, but let’s look at it part by partAPI Module

The API module will host our application code, from models, routes, schemas and controllers if needed (though I usually don’t create those).

  • The models are the data descriptor of our application, in many cases related to the database model, for example when using sqlalchemy, though they can be any class which represents the structure of our data.
  • The routes are the paths to our application (e.g. /api/home or /api/users) and it’s where we will define the route logic, data retrieval, insertion, updates, etc.
  • The schemas are the views (serializers) for our data structures. We should have at least one schema per model. The schemas will have it’s own definition as we will see later.

Route example

home.py

from http import HTTPStatus
from flask import Blueprint
from flasgger import swag_from
from api.model.welcome import WelcomeModel
from api.schema.welcome import WelcomeSchema

home_api = Blueprint('api', __name__ )

@home_api.route('/')
@swag_from({
    'responses': {
        HTTPStatus.OK.value: {
            'description': 'Welcome to the Flask Starter Kit',
            'schema': WelcomeSchema
        }
    }
})
def welcome():
    """
    1 liner about the route
    A more detailed description of the endpoint
    ---
    """
    result = WelcomeModel()
    return WelcomeSchema().dump(result), 200

This is an example of the most basic definition for a route, let’s take a look into it line by line:

home_api = Blueprint('api', __name__ )

Blueprint creation, I like to separate the application in different blueprints, where I provide at the beginning of the file the parent route, in this case /api and all the subsequent routes will be relative to this one.

@home_api.route('/')
@swag_from({
    'responses': {
        HTTPStatus.OK.value: {
            'description': 'Welcome to the Flask Starter Kit',
            'schema': WelcomeSchema
        }
    }
})
def welcome():
    """
    1 liner about the route
    A more detailed description of the endpoint
    ---
    """
    result = WelcomeModel()
    return WelcomeSchema().dump(result), 200

Next is the route itself, in this case we first define the route for the application, there are several ways to do this, and they are very well covered in the flask docs.Right after the route definition we need to provide information for the swagger documentation. I’ve set an example where we define the response object, and the endpoint description as string literals, but there’s more you can do if you follow the official documentation.

Then we need to place the code for our application, to eventually return an object from the API. To serialize the object we use our schemas as can be seen on the code.

Schemas

The schemas are a very important part of this setup, and they are covered in detail in the flask-marshmallow documentation, but in essence a schema is a class that defines the properties and relationships among models and others in so that python can serialize the objects.

Here are some sample schemas from my blockchain code.

from flask_marshmallow import Schema
from marshmallow.fields import Nested, Str, Number
from api.schema.transaction import TransactionSchema

class BlockSchema(Schema):
    class Meta:
        # Fields to expose
        fields = ["index", "timestamp", "transactions", "nonce", "previous_hash", "hash"]

    index = Number()
    nonce = Str()
    timestamp = Number()
    previous_hash = Str()
    hash = Str()
    transactions = Nested(TransactionSchema, many=True)


from flask_marshmallow import Schema
from marshmallow.fields import Nested
from api.schema.block import BlockSchema

class BlockchainSchema(Schema):
    class Meta:
        # Fields to expose
        fields = ["blockchain"]

    blockchain = Nested(BlockSchema, many=True)

Tests

As with any other application, tests are super important, and I could have an entire post discussing why. But here we will keep it simple. For tests I’m simply using unittest module from python, and I try to build tests for each component. Here is an example for our home route:

from unittest import TestCase
from app import create_app

class TestWelcome(TestCase):
    def setUp(self):
        self.app = create_app().test_client()

    def test_welcome(self):
        """
        Tests the route screen message
        """
        rv = self.app.get('/api/')

        # If we recalculate the hash on the block we should get the same result as we have stored
        self.assertEqual({"message": 'Hello World!'}, rv.get_json())

Application

Finally, we need the place where we glue it all together, and we create our python API

from flask import Flask
from flasgger import Swagger
from api.route.home import home_api

def create_app():
    app = Flask( __name__ )

    app.config['SWAGGER'] = {
        'title': 'Flask API Starter Kit',
    }
    swagger = Swagger(app)

    app.register_blueprint(home_api, url_prefix='/api')

    return app

if __name__ == ' __main__':
    from argparse import ArgumentParser

    parser = ArgumentParser()
    parser.add_argument('-p', '--port', default=5000, type=int, help='port to listen on')
    args = parser.parse_args()
    port = args.port

    app = create_app()

    app.run(host='0.0.0.0', port=port)

In the file app.py we define the python flask application. Inside the create_app function you would need to specify your app name for the swagger configuration, each one of your blueprints, any other initialization step such as the db connection, all that would happen here, and there are good examples in the flask quick starter.

Environment Set Up

Check out the code from flask-api-starter-kit

Install requirements pipenv install

Start the server with: pipenv run python -m flask run

Run tests: pipenv run python -m unittest

Visit http://localhost/api for the home api

Visit http://localhost/apidocs for the swagger documentation

Swagger Doc UI Example

Swagger Doc UI Example


I hope this tutorial and project help you build your next API and as usual please let me know if you have any other ideas on twitter at @livecodestream, or simply create an issue, or a PR on the github project.

Thanks for reading!

Top comments (0)