Building a Python Flask API: Top Solutions for Creating Powerful APIs

Jennie Lee
11 min readMar 23, 2024

--

Looking for a Postman alternative?

Try APIDog, the Most Customizable Postman Alternative, where you can connect to thousands of APIs right now!

Introduction to the Flask Factory Pattern

One of the key components of a Flask application is the Flask app object, which is responsible for handling incoming requests and sending responses. In this article, we will explore the Flask Factory Pattern, which is a recommended method for creating the Flask app object.

Creating a Flask app object

Before diving into the Flask Factory Pattern, let’s quickly recap how to create a Flask app object. In its simplest form, you can create a Flask app object like this:

from flask import Flask

app = Flask(__name__)

Here, we import the Flask class from the flask module and create an instance of it. The __name__ parameter is used to resolve resources such as templates and static files.

Two methods for creating the app object: file creation and factory method

There are two common methods for creating the Flask app object. The first method is to create it in a file, typically named app.py or wsgi.py. This is a straightforward approach, but it has a limitation: you can only have a single Flask app object in your project.

The second method, which is the focus of this article, is to use a factory method to create the app object. In this approach, the app object is created dynamically, allowing for more flexibility and better organization in larger projects.

Recommendation for using the factory method

The factory method provides a cleaner way to create the Flask app object and is recommended for most projects. It allows you to decouple the app object creation from the rest of your project, making it easier to test and maintain. Additionally, it enables you to create multiple instances of the app object if needed.

Example of using the factory method to create the Flask app object

To illustrate how to use the factory method, let’s consider an example where our Flask app has a single endpoint that returns a JSON response with a simple message.

First, let’s create a file called app.py and define a function called create_app that will serve as our factory method:

from flask import Flask, jsonify

def create_app():
app = Flask(__name__)

@app.route('/')
def hello():
message = {'message': 'Hello, World!'}
return jsonify(message)

return app

In this example, the create_app function creates a Flask app object, defines a single route, and returns the app object. The route / maps to a function called hello that returns a JSON response with a message.

We can then use the create_app function to create the Flask app object in another file, such as run.py:

from app import create_app

app = create_app()

if __name__ == '__main__':
app.run()

In this file, we import the create_app function from the app module and use it to create the Flask app object. We also include a check for __name__ == '__main__' to ensure that the app is only run if the file is executed directly.

By using the factory method, we can now easily test our Flask app by importing the create_app function and calling it with a different configuration if needed. This flexibility is especially useful for larger projects where you may have different configurations for different environments.

Running the Flask app

Once we have created the Flask app object, we can run the app and start handling requests. There are two common ways to run a Flask app: using the Flask development server on the command line or running the app in an integrated development environment (IDE) such as PyCharm.

Using the Flask development server on the command line

To run the Flask app using the development server provided by Flask, you can navigate to the project directory in the command line and run the following command:

$ flask run

This automatically finds the Flask app object and starts the development server, which listens for incoming requests. You can then access the app in your browser at http://localhost:5000/ (assuming the default Flask port).

Note that this method is convenient for development purposes, but it is not recommended for production deployment. Flask’s development server is not designed to handle high loads or secure applications.

Running the app in PyCharm IDE

If you prefer using an IDE for development, PyCharm is a popular choice for Python projects. To run the Flask app in PyCharm, you can follow these steps:

  1. Open your Flask project in PyCharm.
  2. Right-click on the run.py file (or the file that runs your Flask app) and select "Run 'file_name'".
  3. PyCharm will automatically detect the Flask app object and run it using a built-in server.
  4. You can access the app in your browser at http://localhost:5000/ (or a different port if specified).

Running the app in PyCharm allows for a more integrated development experience, as you can easily debug your Flask application and take advantage of other PyCharm features.

Writing docstrings for Python functions

In addition to creating a Flask app object and running the app, it is crucial to document your code properly. Docstrings serve as a form of inline documentation and help other developers understand how to use your code. They also provide useful information when using tools such as IDE autocompletion or documentation generators.

Importance of docstrings for code documentation

Docstrings are essential for maintaining clean and readable code. By explaining the purpose and usage of each function, you make it much easier for other developers (including your future self) to understand and use your code.

A properly written docstring should describe the function’s purpose, its parameters and return values, and any important side effects or exceptions that may occur. It helps answer questions such as “What does this function do?” and “How should I use it?”

Different formats for writing docstrings

There are several formats for writing docstrings in Python, with the most common ones being the Google-style and the reStructuredText (reST) style. Both styles have their advantages and are widely recognized by the Python community.

For example, here is a docstring written in the Google-style format for the hello function we defined earlier:

def hello():
"""Returns a JSON response with a hello message."""
message = {'message': 'Hello, World!'}
return jsonify(message)

Notice how the docstring is enclosed in triple quotes and provides a brief description of the function’s behavior.

Example of a docstring for the create_app function

Let’s update the create_app function from our previous example with a docstring. We will use the reStructuredText (reST) format, which is also supported by popular tools such as Sphinx for generating documentation:

def create_app():
"""Creates and configures the Flask app object.

Returns:
Flask: The created Flask app object.
"""
app = Flask(__name__)

@app.route('/')
def hello():
message = {'message': 'Hello, World!'}
return jsonify(message)

return app

In this docstring, we provide a more detailed description of the create_app function and specify its return type (Flask). This information can be valuable for other developers who want to understand how the function works and what it returns.

By following a consistent docstring format and documenting your code well, you can greatly improve the readability and maintainability of your Flask application.

Type annotations in Python

Type annotations are another important aspect of Python code documentation. They allow you to specify the expected types for function parameters, return values, and variables, improving code clarity and catching potential bugs.

Importance of type annotations for code clarity and quality

In dynamically typed languages like Python, type annotations can help catch common errors and provide better IDE support. By explicitly specifying types, you make it clear what kind of data the function expects or returns, reducing the risk of passing or receiving incorrect types.

Furthermore, type annotations enable static type checkers and linters to analyze your code and provide additional checks and suggestions. Tools such as mypy, Pyright, and PyCharm’s type checker can help catch subtle bugs and enforce good coding practices.

Benefits of type annotations

Type annotations offer several benefits when working with Python applications:

  • Improved code documentation: By stating the expected types, you provide extra information on how to use the function without needing to consult the implementation.
  • Better code completion and navigation: IDEs can use type annotations to offer more accurate code completion suggestions and navigate to the source definition.
  • Static analysis and bug catching: Static type checkers can analyze your code and catch potential type-related bugs before runtime, reducing the need for extensive unit testing.
  • Refactoring and maintenance: With type annotations, you can quickly identify the effects of changing a function’s signature and refactor your code accordingly, confident that the type checker will catch any issues.

Example of a type annotation for the create_app function

Let’s update the create_app function once again and add type annotations to the parameters and return value:

from flask import Flask, jsonify

def create_app() -> Flask:
app: Flask = Flask(__name__)

@app.route('/')
def hello() -> Dict[str, str]:
message: Dict[str, str] = {'message': 'Hello, World!'}
return jsonify(message)

return app

In this example, we specify that the create_app function returns an instance of Flask by adding -> Flask after the function definition. We also add type annotations to the app variable and the message variable inside the hello function.

With type annotations in place, both static type checkers and IDEs can provide feedback on potential type mismatches and help catch errors early.

Introduction to Test Driven Development (TDD)

Now that we have covered the basics of creating a Flask app, documenting our code, and using type annotations, let’s dive into Test Driven Development (TDD). TDD is a methodology that involves writing tests before implementing the code.

Explanation of TDD and its benefits

TDD is based on the principle of writing tests that define the expected behavior of the code before actually implementing that behavior. The typical TDD workflow involves the following steps:

  1. Write a failing test case that describes the desired behavior of the code.
  2. Run the test and observe it fail.
  3. Implement the code that satisfies the test case.
  4. Run the test again and observe it pass.
  5. Refactor the code if necessary and repeat the process.

The benefits of TDD include:

  • Improved code quality: TDD encourages you to think thoroughly about the desired behavior of your code and write tests that cover various scenarios. This leads to more robust and reliable code.
  • Better test coverage: By writing tests first, you ensure that every piece of functionality is covered. This reduces the risk of regressions and allows for easier code maintenance.
  • Faster debugging and development: If a test fails, the cause of the issue is often easier to identify since you have a clear understanding of the expected behavior. This speeds up the debugging process and helps streamline development.

Introduction to the pytest framework for writing tests

When it comes to writing tests in Python, the pytest framework is a popular choice. Pytest provides a simple and intuitive way to write tests and offers a wide range of features, including test discovery, fixtures, parametric testing, and plugins.

To use pytest, you need to install it as a development dependency in your Flask project. You can do this using pip:

$ pip install pytest

Additionally, we will also install the pytest-flask package, which provides helpful utilities for testing Flask applications:

$ pip install pytest-flask

Configuring PyCharm to use pytest as the default test runner

If you are using PyCharm as your IDE, you can configure it to use pytest as the default test runner. This allows you to run tests directly from the IDE and take advantage of PyCharm’s testing features.

To configure PyCharm to use pytest, follow these steps:

  1. Open your Flask project in PyCharm.
  2. Go to Preferences or Settings (depending on your operating system).
  3. Navigate to Python Integrated Tools > Testing.
  4. Select pytest as the default test runner.
  5. Click Apply or OK to save the changes.

With pytest configured as the default test runner, you can now write and run tests directly from PyCharm.

Creating a Blueprint in Flask

As your Flask application grows, you may find it helpful to organize your routes and views into logical groups. Blueprints provide a way to do this by grouping related routes and views into reusable components.

Explanation of Blueprints and their purpose

Blueprints are a way to define a collection of routes and views that can be registered with a Flask app. They allow you to encapsulate related functionality and promote modularity in your codebase.

A Blueprint can have its own middleware, error handlers, and static files, making it a powerful tool for structuring larger Flask applications.

Example of creating a Blueprint for a health check endpoint

Let’s walk through an example of creating a Blueprint for a health check endpoint that returns a simple JSON response.

First, we’ll create a new package called views in our project directory. Inside the views package, we'll create a new file called health.py. Here's an example implementation of the health check Blueprint:

from flask import Blueprint, jsonify

health_bp = Blueprint('health', __name__)

@health_bp.route('/health')
def health():
return jsonify({'status': 'ok'})

In this example, we import the Blueprint class from the flask module and create a Blueprint object called health_bp.

The route decorator is then used to define a route for the health check endpoint. When a request is made to /health, the health function is executed and returns a JSON response with a status of "ok".

Writing a test for the health check endpoint

Now that we have created the health check endpoint using a Blueprint, we can write a test to verify its behavior.

Create a new file called test_health.py in a tests package in your project directory. Add the following code to the test_health.py file:

from flask import Flask
from pytest import fixture
from my_app.views.health import health_bp

@fixture
def app() -> Flask:
app = Flask(__name__)
app.register_blueprint(health_bp)
return app

def test_health(app: Flask):
client = app.test_client()
response = client.get('/health')

assert response.status_code == 200
assert response.get_json() == {'status': 'ok'}

In this test, we create a fixture called app that returns an instance of the Flask app with the health check Blueprint registered.

The test_health function then uses the test_client method of the Flask app to simulate a request to the /health endpoint. We assert that the response has a status code of 200 and that the JSON response matches the expected value.

Registering the Blueprint to the Flask app

To make the health check endpoint accessible, we need to register the Blueprint to the Flask app. In the app.py file, add the following line before running the app:

app.register_blueprint(health_bp)

By registering the health Blueprint to the Flask app, all routes defined in the Blueprint become accessible.

Running the test

To run the test we created for the health check endpoint, navigate to your project directory in the command line and run the following command:

$ pytest

If the test passes, you should see an output similar to the following:

================================ test session starts =================================
platform linux -- Python 3.8.2, pytest-6.2.3, py-1.10.0, pluggy-0.13.1
...
collected 1 item

tests/test_health.py . [100%]

================================ 1 passed in 0.12s ==================================

With the Blueprint registered and the test passing, the health check endpoint is ready to be used in your Flask application.

Conclusion

In this article, we explored the Flask Factory Pattern for creating powerful Python Flask APIs. We learned how to create the Flask app object using the factory method, and we discussed the advantages of this approach over traditional file creation.

We also covered different aspects of code documentation, including writing docstrings and using type annotations. Proper documentation is crucial for maintaining clean and understandable code, and type annotations can improve code clarity and catch potential bugs.

Additionally, we introduced the concept of Test Driven Development (TDD) and showed how to use the pytest framework for writing tests. TDD can lead to higher code quality and faster development, and pytest provides a robust toolset for testing Flask applications.

Finally, we explored Blueprints in Flask and showed how to use them to group related routes and views. Blueprints help organize larger Flask applications and promote modularity and maintainability.

By following the techniques and best practices outlined in this article, you can create powerful Python Flask APIs that are well-documented, thoroughly tested, and maintainable. Remember to refer to the documentation and official Flask resources for more detailed guidance and examples as you continue your journey with Flask.

Looking for a Postman alternative?

Try APIDog, the Most Customizable Postman Alternative, where you can connect to thousands of APIs right now!

--

--