FastAPI Development environment set up
Table of Contents
Preface
Emacs text editor is powerful enough to simplify the task of FastAPI
development by providing its faculties inside Org-mode Babel Python
code blocks.
Prerequisites
A separate venv
pyvenv-create pyvenv-activate
Install FastAPI
pip list
Package Version
---------- -------
pip 23.0.1
setuptools 66.1.1
wheel 0.38.4
pip install fastapi
Collecting fastapi Downloading fastapi-0.111.1-py3-none-any.whl (92 kB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 92.2/92.2 kB 1.2 MB/s eta 0:00:00 Collecting starlette<0.38.0,>=0.37.2 Downloading starlette-0.37.2-py3-none-any.whl (71 kB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 71.9/71.9 kB 1.3 MB/s eta 0:00:00 Collecting pydantic!=1.8,!=1.8.1,!=2.0.0,!=2.0.1,!=2.1.0,<3.0.0,>=1.7.4 Using cached pydantic-2.8.2-py3-none-any.whl (423 kB) Collecting typing-extensions>=4.8.0 Using cached typing_extensions-4.12.2-py3-none-any.whl (37 kB) Collecting fastapi-cli>=0.0.2 Downloading fastapi_cli-0.0.4-py3-none-any.whl (9.5 kB) Collecting httpx>=0.23.0 Using cached httpx-0.27.0-py3-none-any.whl (75 kB) Collecting jinja2>=2.11.2 Downloading jinja2-3.1.4-py3-none-any.whl (133 kB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 133.3/133.3 kB 1.5 MB/s eta 0:00:00 Collecting python-multipart>=0.0.7 Downloading python_multipart-0.0.9-py3-none-any.whl (22 kB) Collecting email_validator>=2.0.0 Downloading email_validator-2.2.0-py3-none-any.whl (33 kB) Collecting uvicorn[standard]>=0.12.0 Downloading uvicorn-0.30.1-py3-none-any.whl (62 kB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 62.4/62.4 kB 2.4 MB/s eta 0:00:00 Collecting dnspython>=2.0.0 Downloading dnspython-2.6.1-py3-none-any.whl (307 kB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 307.7/307.7 kB 2.4 MB/s eta 0:00:00 Collecting idna>=2.0.0 Using cached idna-3.7-py3-none-any.whl (66 kB) Collecting typer>=0.12.3 Downloading typer-0.12.3-py3-none-any.whl (47 kB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 47.2/47.2 kB 1.1 MB/s eta 0:00:00 Collecting anyio Using cached anyio-4.4.0-py3-none-any.whl (86 kB) Collecting certifi Using cached certifi-2024.7.4-py3-none-any.whl (162 kB) Collecting httpcore==1.* Using cached httpcore-1.0.5-py3-none-any.whl (77 kB) Collecting sniffio Using cached sniffio-1.3.1-py3-none-any.whl (10 kB) Collecting h11<0.15,>=0.13 Using cached h11-0.14.0-py3-none-any.whl (58 kB) Collecting MarkupSafe>=2.0 Downloading MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (28 kB) Collecting annotated-types>=0.4.0 Using cached annotated_types-0.7.0-py3-none-any.whl (13 kB) Collecting pydantic-core==2.20.1 Using cached pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (2.1 MB) Collecting click>=7.0 Downloading click-8.1.7-py3-none-any.whl (97 kB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 97.9/97.9 kB 1.6 MB/s eta 0:00:00 Collecting httptools>=0.5.0 Downloading httptools-0.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl (318 kB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 318.5/318.5 kB 1.8 MB/s eta 0:00:00 Collecting python-dotenv>=0.13 Downloading python_dotenv-1.0.1-py3-none-any.whl (19 kB) Collecting pyyaml>=5.1 Downloading PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (757 kB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 757.7/757.7 kB 2.2 MB/s eta 0:00:00 Collecting uvloop!=0.15.0,!=0.15.1,>=0.14.0 Downloading uvloop-0.19.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (3.5 MB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3.5/3.5 MB 564.1 kB/s eta 0:00:00 Collecting watchfiles>=0.13 Downloading watchfiles-0.22.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.2 MB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.2/1.2 MB 497.8 kB/s eta 0:00:00 Collecting websockets>=10.4 Downloading websockets-12.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl (130 kB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 130.9/130.9 kB 497.4 kB/s eta 0:00:00 Collecting shellingham>=1.3.0 Downloading shellingham-1.5.4-py2.py3-none-any.whl (9.8 kB) Collecting rich>=10.11.0 Downloading rich-13.7.1-py3-none-any.whl (240 kB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 240.7/240.7 kB 584.5 kB/s eta 0:00:00 Collecting markdown-it-py>=2.2.0 Downloading markdown_it_py-3.0.0-py3-none-any.whl (87 kB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 87.5/87.5 kB 703.1 kB/s eta 0:00:00 Collecting pygments<3.0.0,>=2.13.0 Downloading pygments-2.18.0-py3-none-any.whl (1.2 MB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.2/1.2 MB 744.5 kB/s eta 0:00:00 Collecting mdurl~=0.1 Downloading mdurl-0.1.2-py3-none-any.whl (10.0 kB) Installing collected packages: websockets, uvloop, typing-extensions, sniffio, shellingham, pyyaml, python-multipart, python-dotenv, pygments, mdurl, MarkupSafe, idna, httptools, h11, dnspython, click, certifi, annotated-types, uvicorn, pydantic-core, markdown-it-py, jinja2, httpcore, email_validator, anyio, watchfiles, starlette, rich, pydantic, httpx, typer, fastapi-cli, fastapi Successfully installed MarkupSafe-2.1.5 annotated-types-0.7.0 anyio-4.4.0 certifi-2024.7.4 click-8.1.7 dnspython-2.6.1 email_validator-2.2.0 fastapi-0.111.1 fastapi-cli-0.0.4 h11-0.14.0 httpcore-1.0.5 httptools-0.6.1 httpx-0.27.0 idna-3.7 jinja2-3.1.4 markdown-it-py-3.0.0 mdurl-0.1.2 pydantic-2.8.2 pydantic-core-2.20.1 pygments-2.18.0 python-dotenv-1.0.1 python-multipart-0.0.9 pyyaml-6.0.1 rich-13.7.1 shellingham-1.5.4 sniffio-1.3.1 starlette-0.37.2 typer-0.12.3 typing-extensions-4.12.2 uvicorn-0.30.1 uvloop-0.19.0 watchfiles-0.22.0 websockets-12.0
pip list
Package Version ----------------- -------- annotated-types 0.7.0 anyio 4.4.0 certifi 2024.7.4 click 8.1.7 dnspython 2.6.1 email_validator 2.2.0 fastapi 0.111.1 fastapi-cli 0.0.4 h11 0.14.0 httpcore 1.0.5 httptools 0.6.1 httpx 0.27.0 idna 3.7 Jinja2 3.1.4 markdown-it-py 3.0.0 MarkupSafe 2.1.5 mdurl 0.1.2 pip 23.0.1 pydantic 2.8.2 pydantic_core 2.20.1 Pygments 2.18.0 python-dotenv 1.0.1 python-multipart 0.0.9 PyYAML 6.0.1 rich 13.7.1 setuptools 66.1.1 shellingham 1.5.4 sniffio 1.3.1 starlette 0.37.2 typer 0.12.3 typing_extensions 4.12.2 uvicorn 0.30.1 uvloop 0.19.0 watchfiles 0.22.0 websockets 12.0 wheel 0.38.4
A Primitive App
from typing import Union from fastapi import FastAPI app = FastAPI() @app.get("/") def read_root(): return {"Hello": "World"} @app.get("/items/{item_id}") def read_item(item_id: int, q: Union[str, None] = None): return {"item_id": item_id, "q": q}
Manual testing
As it is explained in your browser.
Enter uinttest
I wrote a primitive FastAPI
app:
from typing import Union from fastapi import FastAPI app = FastAPI() @app.get("/") def read_root(): return {"Hello": "World"} @app.get("/items/{item_id}") def read_item(item_id: int, q: Union[str, None] = None): return {"item_id": item_id, "q": q}
can you write the unittest
for it?
Certainly! To write `unittest` tests for your FastAPI application, we can use the `TestClient` provided by FastAPI which is a wrapper around Starlette's `TestClient`. This allows us to simulate HTTP requests to our endpoints and verify their responses. Below is how you can structure your unit tests using the `unittest` module:
FastAPI Application
from typing import Union from fastapi import FastAPI app = FastAPI() @app.get("/") def read_root(): return {"Hello": "World"} @app.get("/items/{item_id}") def read_item(item_id: int, q: Union[str, None] = None): return {"item_id": item_id, "q": q}
Unit Tests
import unittest from fastapi.testclient import TestClient from code.awesomeapp.main import app # Assuming your FastAPI app is saved in main.py client = TestClient(app) class TestFastAPI(unittest.TestCase): def test_read_root(self): response = client.get("/") self.assertEqual(response.status_code, 200) self.assertDictEqual(response.json(), {"Hello": "World"}) def test_read_item_with_query(self): response = client.get("/items/42?q=somequery") self.assertEqual(response.status_code, 200) self.assertDictEqual(response.json(), {"item_id": 42, "q": "somequery"}) def test_read_item_without_query(self): response = client.get("/items/42") self.assertEqual(response.status_code, 200) self.assertDictEqual(response.json(), {"item_id": 42, "q": None}) if __name__ == "__main__": unittest.main()
... ---------------------------------------------------------------------- Ran 3 tests in 0.014s OK
Explanation
- Importing FastAPI App:
- Import the FastAPI app instance from the file where it is defined (
main
in this case).
- Import the FastAPI app instance from the file where it is defined (
- Test Client:
- Create an instance of
TestClient
, passing in the FastAPI app object. This client will be used to make requests to the endpoints for testing.
- Create an instance of
- Unit Test Class:
- Define a class
TestFastAPI
that inherits fromunittest.TestCase
. This class will contain all our test methods.
- Define a class
- Test Methods:
test_read_root
:- This method tests the root endpoint
/
. We expect a 200 status code and a JSON response{"Hello": "World"}
.
- This method tests the root endpoint
test_read_item_with_query
:- This method tests the
/items/{item_id}
endpoint with an additional query parameterq
. We expect a 200 status code and a JSON response containing the item ID and the query parameter.
- This method tests the
test_read_item_without_query
:- This method tests the
/items/{item_id}
endpoint without the query parameterq
. We expect a 200 status code and a JSON response containing the item ID withq
asNone
.
- This method tests the
Running the Tests
To run the tests, you can:
- Save the application code in
main.py
. - Save the test code in a separate file, e.g.,
test_main.py
. Execute the tests using the
unittest
command in your terminal:cd code/awesomeapp/ python -m unittest test_main.py
... ---------------------------------------------------------------------- Ran 3 tests in 0.015s OK
Alternatively, if you're working within Emacs with Org-mode and Babel, you can execute the test code block within the Org document.
This setup will help you ensure that your FastAPI application works as expected.