Skip to content

Up and Running FastAPI

FastAPI is a python framework to create high performance APIs, ready for production. The FastAPI project has been inspired by some well-known predecessors: Flask, Django, Requests, Marshmallow, Swagger,... (here you can find more information about this topic).

One of the key features is that it is very fast to code. And this is the goal of this first note, to start a FastAPI project from scratch.

Create the project structure, virtual environment and initialize the git repository

The first step is to create the base directory and some basic files:

mkdir fastapi_basics
cd fastapi_basics
mkdir -p tests
touch .gitignore requirements.txt

In this case, we'll use a virtual environment to run the project. A better practice would be to use a docker image as a container, but this is out of the scope of those notes. To create a Python 3 virtual environment is very simple:

python -m venv ~/.venv/fastapi_basics
source ~/.venv/fastapi_basics/bin/activate

And the third step to create the main skeleton of the project is to initialize the git repository, create the branch up_and_running on, and switch into it:

git init
git checkout -b up_and_running

A good practice is to setup the .gitignore file. Now is the moment. In my case I added these lines:

.idea
*.pyc

Install needed packages

With the base of the project ready, it's time to start thinking on the FastAPI framework. Head into the requirements.txt file and update it with the following code:

fastapi==0.61.0
pytest==6.0.1

With the requirements file updated, let's install the required python packages to start testing our initial note..

pip install -r requirements.txt 

The previous command, install the 2 specified packages and its dependencies, like pydantic or starlette in the virtual environment.

Before to start writing any test, let's save the changes to git:

git add .
git commit -m "Initial requirements and .gitignore"

Create first test

We have defined the basic structure of the test project, we have setup the environment, and now it's time to start writing some real python code! We'll write the first test. The goal of this first test is to briefly show the concepts behind TDD.

First, we define our basic goal: to create a very simple function to sum two integer numbers and return its sum.

The specifications are very simple: - the input are two integer numbers: 'x' and 'y'. - the output is an integer number, resulting of making the sum of the two input numbers.

Second, we write our first test. But previously, we need to create the test file:

touch tests/test_00_using_tests_for_everything.py

Let's write 3 tests:

  • test_sum_integers_with_correct_values_successfully
  • test_sum_integers_with_strings_fails
  • test_sum_integers_with_floats_fails

tests/test_00_using_tests_for_everything.py

+import pytest
+
+
+def sum_integers(x, y):
+    return x + y
+
+
+def test_sum_integers_with_correct_values_successfully():
+    result = sum_integers(1, 3)
+    assert result == 4
+
+
+def test_sum_integers_with_strings_fails():
+    with pytest.raises(ValueError):
+        sum_integers('1', '3')
+
+
+def test_sum_integers_with_floats_fails():
+    with pytest.raises(ValueError):
+        sum_integers(1.0, 3.0)

If we run them, we can see that the first test pass, the other two fail.

tests/test_00_using_tests_for_everything.py:12

>           sum_integers('1', '3')
E           Failed: DID NOT RAISE <class 'ValueError'>

tests/test_00_using_tests_for_everything.py:17

>           sum_integers(1.0, 3.0)
E           Failed: DID NOT RAISE <class 'ValueError'>

Let's add a simple validation inside the sum_integers function, to check that input params are integers:

tests/test_00_using_tests_for_everything.py

@@ -1,14 +1,16 @@

 import pytest


-def sum_integers(x, y):
+def sum_integers(x: int, y: int) -> int:
+    if not isinstance(x, int) or not isinstance(y, int):
+        raise ValueError("Parameters 'x' and 'y' must be integers.")
     return x + y


 def test_sum_integers_with_correct_values_successfully():
     result = sum_integers(1, 3)
     assert result == 4


 def test_sum_integers_with_strings_fails():
     with pytest.raises(ValueError):

And now, if we run the tests again:

pytest tests/test_00_using_tests_for_everything.py -v

we see that all the tests go green:

========================== test session starts ===========================
collected 3 items                                                                                                                                                            

tests/test_00_using_tests_for_everything.py::test_sum_integers_with_correct_values_successfully PASSED [ 33%]
tests/test_00_using_tests_for_everything.py::test_sum_integers_with_strings_fails PASSED               [ 66%]
tests/test_00_using_tests_for_everything.py::test_sum_integers_with_floats_fails PASSED                [100%]

============================= 3 passed in 0.02s =============================

This is a very simple example of the TDD approach. TDD stands for Test Driven Design, and it means exactly the steps done previously. First, design what you want to achieve, then write the tests and the code to be tested, and run. If everything works, great, if it doesn't, fix the code and check again.