Skip to content

Commit 84ad314

Browse files
committed
Add FastAPI module
1 parent e30fc51 commit 84ad314

File tree

7 files changed

+99
-1
lines changed

7 files changed

+99
-1
lines changed

modules/fastapi/README.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.. autoclass:: testcontainers.fastapi.FastAPIContainer
2+
.. title:: testcontainers.fastapi.FastAPIContainer
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
from typing import Optional
2+
3+
import httpx
4+
5+
from testcontainers.core.generic import SrvContainer
6+
7+
8+
class FastAPIContainer(SrvContainer):
9+
"""
10+
FastAPI container that is based on a custom image.
11+
12+
Example:
13+
14+
.. doctest::
15+
16+
>>> from testcontainers.fastapi import FastAPIContainer
17+
>>> from testcontainers.core.waiting_utils import wait_for_logs
18+
19+
>>> with FastAPIContainer(path="./modules/fastapi/tests/sample", port=80, tag="fastapi:latest") as fastapi:
20+
... delay = wait_for_logs(fastapi, "Uvicorn running on http://0.0.0.0:80")
21+
... client = fastapi.get_client()
22+
... response = client.get("/")
23+
... assert response.status_code == 200
24+
... assert response.json() == {"Status": "Working"}
25+
"""
26+
27+
def __init__(self, path: str, port: int, tag: Optional[str] = None, image_cleanup: bool = True) -> None:
28+
"""
29+
:param path: Path to the FastAPI application.
30+
:param port: Port to expose the FastAPI application.
31+
:param tag: Tag for the image to be built (default: None).
32+
:param image_cleanup: Clean up the image after the container is stopped (default: True).
33+
"""
34+
super().__init__(path, port, tag, image_cleanup)
35+
36+
def get_api_url(self) -> str:
37+
return self._create_connection_url() + "/api/v1/"
38+
39+
def get_client(self) -> httpx.Client:
40+
return httpx.Client(base_url=self.get_api_url())
41+
42+
def get_stdout(self) -> str:
43+
return self.get_logs()[0].decode("utf-8")
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
FROM python:3.9
2+
3+
WORKDIR /app
4+
5+
RUN pip install fastapi
6+
7+
COPY ./app /app
8+
9+
EXPOSE 80
10+
11+
CMD ["fastapi", "run", "main.py", "--port", "80"]

modules/fastapi/tests/sample/app/__init__.py

Whitespace-only changes.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from fastapi import FastAPI
2+
3+
app = FastAPI()
4+
5+
6+
@app.get("/api/v1/")
7+
def read_root():
8+
return {"Status": "Working"}

modules/fastapi/tests/test_fastapi.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import re
2+
import pytest
3+
4+
from testcontainers.fastapi import FastAPIContainer
5+
6+
7+
def test_fastapi_container():
8+
with FastAPIContainer(
9+
path="./modules/fastapi/tests/sample", port=80, tag="fastapi:test", image_cleanup=False
10+
) as fastapi:
11+
assert fastapi.get_container_host_ip() == "localhost"
12+
assert fastapi.internal_port == 80
13+
assert re.match(r"http://localhost:\d+/api/v1/", fastapi.get_api_url())
14+
assert fastapi.get_client().get("/").status_code == 200
15+
assert fastapi.get_client().get("/").json() == {"Status": "Working"}
16+
17+
18+
def test_fastapi_container_no_tag():
19+
with FastAPIContainer(path="./modules/fastapi/tests/sample", port=80, image_cleanup=False) as fastapi:
20+
assert fastapi.get_client().get("/").status_code == 200
21+
assert fastapi.get_client().get("/").json() == {"Status": "Working"}
22+
23+
24+
def test_fastapi_container_no_port():
25+
with pytest.raises(TypeError):
26+
with FastAPIContainer(path="./modules/fastapi/tests/sample", tag="fastapi:test", image_cleanup=False):
27+
pass
28+
29+
30+
def test_fastapi_container_no_path():
31+
with pytest.raises(TypeError):
32+
with FastAPIContainer(port=80, tag="fastapi:test", image_cleanup=True):
33+
pass

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ packages = [
5757
{ include = "testcontainers", from = "modules/registry" },
5858
{ include = "testcontainers", from = "modules/selenium" },
5959
{ include = "testcontainers", from = "modules/vault" },
60-
{ include = "testcontainers", from = "modules/weaviate" }
60+
{ include = "testcontainers", from = "modules/weaviate" },
61+
{ include = "testcontainers", from = "modules/fastapi" },
6162
]
6263

6364
[tool.poetry.urls]

0 commit comments

Comments
 (0)