Skip to content

Commit 8ef441a

Browse files
committed
feat(core): Added FastAPI module
Fix missing doc in index Fix extra Fix missing httpx as optional dependency
1 parent 6f140f1 commit 8ef441a

File tree

9 files changed

+104
-2
lines changed

9 files changed

+104
-2
lines changed

index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ testcontainers-python facilitates the use of Docker containers for functional an
2121
modules/chroma/README
2222
modules/clickhouse/README
2323
modules/elasticsearch/README
24+
modules/fastapi/README
2425
modules/google/README
2526
modules/influxdb/README
2627
modules/k3s/README

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

poetry.lock

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ packages = [
3434
{ include = "testcontainers", from = "modules/chroma" },
3535
{ include = "testcontainers", from = "modules/clickhouse" },
3636
{ include = "testcontainers", from = "modules/elasticsearch" },
37+
{ include = "testcontainers", from = "modules/fastapi" },
3738
{ include = "testcontainers", from = "modules/google" },
3839
{ include = "testcontainers", from = "modules/influxdb" },
3940
{ include = "testcontainers", from = "modules/k3s" },
@@ -57,7 +58,7 @@ packages = [
5758
{ include = "testcontainers", from = "modules/registry" },
5859
{ include = "testcontainers", from = "modules/selenium" },
5960
{ include = "testcontainers", from = "modules/vault" },
60-
{ include = "testcontainers", from = "modules/weaviate" }
61+
{ include = "testcontainers", from = "modules/weaviate" },
6162
]
6263

6364
[tool.poetry.urls]
@@ -99,13 +100,15 @@ weaviate-client = { version = "^4.5.4", optional = true }
99100
chromadb-client = { version = "*", optional = true }
100101
qdrant-client = { version = "*", optional = true }
101102
bcrypt = { version = "*", optional = true }
103+
httpx = { version = "*", optional = true }
102104

103105
[tool.poetry.extras]
104106
arangodb = ["python-arango"]
105107
azurite = ["azure-storage-blob"]
106108
cassandra = []
107109
clickhouse = ["clickhouse-driver"]
108110
elasticsearch = []
111+
fastapi = ["httpx"]
109112
google = ["google-cloud-pubsub", "google-cloud-datastore"]
110113
influxdb = ["influxdb", "influxdb-client"]
111114
k3s = ["kubernetes", "pyyaml"]

0 commit comments

Comments
 (0)