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

  1. Importing FastAPI App:
    • Import the FastAPI app instance from the file where it is defined (main in this case).
  2. 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.
  3. Unit Test Class:
    • Define a class TestFastAPI that inherits from unittest.TestCase. This class will contain all our test methods.
  4. Test Methods:
    • test_read_root:
      • This method tests the root endpoint /. We expect a 200 status code and a JSON response {"Hello": "World"}.
    • test_read_item_with_query:
      • This method tests the /items/{item_id} endpoint with an additional query parameter q. We expect a 200 status code and a JSON response containing the item ID and the query parameter.
    • test_read_item_without_query:
      • This method tests the /items/{item_id} endpoint without the query parameter q. We expect a 200 status code and a JSON response containing the item ID with q as None.

Running the Tests

To run the tests, you can:

  1. Save the application code in main.py.
  2. Save the test code in a separate file, e.g., test_main.py.
  3. 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.