SoftWhat?

Software Engineering tidbits

Organizing Flask-based Web API Directory Structure

Posted at — Jan 15, 2020

Acknowledgements: This post was heavily influenced by Explore Flask book and series of Miguel Grinberg’s posts starting with Designing a RESTful API with Python and Flask.

Still, the above and most of the other examples I found usually refer to directory structure of frontend applications or very simple web APIs. It took me a while to build a big picture of how to build the directory structure of a web API with Flask, hence I thought it might be useful to share. In addition, I wanted to add an example of how to configure a web API for production deployment. One of popular Platform-as-a-Service (PaaS) offerings out there is Cloud Foundry, so I thought it would be useful to provide an example for this platform.

The code is available on GitHub in flask-webapi repository. In this blog post I will walk you through each file and explain the different considerations for choosing the structure and content. Below is the complete directory tree of the code.

.  
|-- .cfignore -> .gitignore  
|-- .gitignore  
|-- LICENSE  
|-- Procfile  
|-- README.md  
|-- api  
|   |-- __init__.py  
|   `-- v1  
|       |-- __init__.py  
|       |-- endpoint.py  
|-- manifest.yml  
|-- requirements.txt  
|-- run.py  
|-- runtime.txt  
`-- tests  
   `-- __init__.py  

.cfignore

This file is used by Cloud Foundry to skip uploading unneeded files for deployment (e.g. locally compiled libraries). For now, it is a soft link to .gitignore file, since there is no difference at this point between what I would like to skip for version control and skip for upload to Cloud Foundry.

.gitignore

Git ignore file - nothing fancy here.

LICENSE

Well, it was auto-generated by GitHub, and I assume the intent is pretty clear.

Procfile

This file is used by Cloud Foundry. One reason to use a Procfile is specify a start command for buildpacks where a default start command is not provided. Some buildpacks, such as Python, that work with a variety of frameworks, do not attempt to provide a default start command, hence we use this file here.

web: gunicorn run:app --bind 0.0.0.0:$PORT --workers 4

I chose Gunicorn since it seems to be one of the popular web/WSGI servers for Python applications based on The Hitchhiker’s Guide to Python (link). The server listens on port defined through PORT environment variable which is assigned a value by Cloud Foundry during deployment. We start with 4 workers based on Gunicorn’s recommendation. You will probably want to read the documentation for more configuration details, but for starter - the run:app combination is of the pattern $(MODULE_NAME):$(VARIABLE_NAME). The variable name refers to a WSGI callable (created by Flask in our case) that should be found in the specified module.

Even if your Cloud Foundry deployment doesn’t limit the number of cores available to each container, it would be more optimal for platform’s resources utilization to set the number of workers assuming a single core is available. If we put a monitoring system in place, the application can be scaled out to handle a spike in requests, instead of increasing a number of workers of a specific instance. You can also use App-AutoScaler, that provides auto scaling capabilities for Cloud Foundry applications. There is also a sample project that demonstrates auto-scaling workers in a producer-consumer scenario on Cloud Foundry.

README.md

You can add a README.md (in Markdown format) file to your repository to tell other people why your project is useful, what they can do with your project, and how they can use it.

api/__init__.py

Contains the create_app function. The reason it is defined in __init__.py is to have the package name (api) as app name. The name of the package is used to resolve resources from inside the package. See more details in Flask Application Object API documentation.

api/v1/endpoint.py

This is a simple endpoint example that uses Flask-RESTful - an extension for Flask that adds support for quickly building REST APIs. It is a lightweight abstraction that works with your existing ORM/libraries. Flask-RESTful encourages best practices with minimal setup.

Regarding the directory structure - there are many opinions out there about how to version the API (through URL, header and combinations). The best resource I found so far is Best Practices for Designing a Pragmatic RESTful API by Vinay Sahni. It suggests that major version should be managed through the URL, and minor changes through headers. For me, it is also in line with Semantic Versioning - changing a major version is not backwards compatible and should be very explicit, whereas the rest of the changes should be backwards compatible or gently deprecate features. Shared code between major versions will be placed outside of the v1, v2, ... directories (e.g. utility modules). That can be done upfront when coding v1 or refactored when working on v2 to support reuse.

manifest.yml

Cloud Foundry application manifests instruct cf push (Cloud Foundry CLI) command how to configure the application deployment - number of application instances to create, amount of memory to allocate and more.

requirements.txt

Requirements files contain a list of items to be installed using pip install. Cloud Foundry Python buildpack looks up this file, and installs the dependencies during deployment if the file exists.

run.py

I use the Application Factories approach. It is useful for testing - you can create instances of the application with different settings to test multiple use cases. In this example, create_app function doesn’t get any arguments. To enable testing with different settings, it should be passed a configuration - either a path to configuration file or an object containing the configuration. app object is defined in global scope and not inside the if block to enable running the app with Gunicorn in production (see Procfile section above), in addition to running with Flask development server.

runtime.txt

Allows to override the default Python version in Cloud Foundry Python buildpack. I recommend to use this file even for specific buildpack versions. Otherwise a developer needs to find what is the default Python version of the specific buildpack version, which can be easily avoided using this file and is also more explicit.

tests

See Testing Flask Applications in Flask guide.