Skip to content

Commit 01069bc

Browse files
Add FastAPI: async Python web framework on Uvicorn (~80k⭐)
- FastAPI 0.115 + Uvicorn + uvloop + Gunicorn multi-worker - orjson for fast JSON serialization - Async handlers with sync SQLite via thread-local connections - gzip compression at level 1 for /compression endpoint - All standard endpoints: /pipeline, /baseline11, /baseline2, /json, /compression, /db, /upload
1 parent 11f5747 commit 01069bc

5 files changed

Lines changed: 194 additions & 0 deletions

File tree

frameworks/fastapi/Dockerfile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
FROM python:3.13-slim
2+
WORKDIR /app
3+
COPY requirements.txt .
4+
RUN pip install --no-cache-dir -r requirements.txt
5+
COPY . .
6+
EXPOSE 8080
7+
CMD ["gunicorn", "-c", "gunicorn_conf.py", "app:app"]

frameworks/fastapi/app.py

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
import gzip
2+
import json
3+
import os
4+
import sqlite3
5+
import threading
6+
7+
import orjson
8+
from fastapi import FastAPI, Request, Response
9+
10+
app = FastAPI()
11+
12+
# ── Dataset ──────────────────────────────────────────────────────────
13+
dataset_items = None
14+
dataset_path = os.environ.get("DATASET_PATH", "/data/dataset.json")
15+
try:
16+
with open(dataset_path) as f:
17+
dataset_items = json.load(f)
18+
except Exception:
19+
pass
20+
21+
# Large dataset for compression (pre-serialised)
22+
large_json_buf: bytes | None = None
23+
try:
24+
with open("/data/dataset-large.json") as f:
25+
raw = json.load(f)
26+
items = []
27+
for d in raw:
28+
item = dict(d)
29+
item["total"] = round(d["price"] * d["quantity"] * 100) / 100
30+
items.append(item)
31+
large_json_buf = orjson.dumps({"items": items, "count": len(items)})
32+
except Exception:
33+
pass
34+
35+
# ── SQLite (thread-local, sync — runs in threadpool via run_in_executor) ──
36+
db_available = os.path.exists("/data/benchmark.db")
37+
DB_QUERY = (
38+
"SELECT id, name, category, price, quantity, active, tags, rating_score, rating_count "
39+
"FROM items WHERE price BETWEEN ? AND ? LIMIT 50"
40+
)
41+
_local = threading.local()
42+
43+
44+
def _get_db() -> sqlite3.Connection:
45+
conn = getattr(_local, "conn", None)
46+
if conn is None:
47+
conn = sqlite3.connect("/data/benchmark.db", uri=True)
48+
conn.execute("PRAGMA mmap_size=268435456")
49+
conn.row_factory = sqlite3.Row
50+
_local.conn = conn
51+
return conn
52+
53+
54+
# ── Helpers ──────────────────────────────────────────────────────────
55+
def _text(body: str | bytes, status: int = 200) -> Response:
56+
return Response(
57+
content=body,
58+
status_code=status,
59+
media_type="text/plain",
60+
headers={"Server": "fastapi"},
61+
)
62+
63+
64+
def _json_resp(body: bytes, status: int = 200, extra_headers: dict | None = None) -> Response:
65+
headers = {"Server": "fastapi"}
66+
if extra_headers:
67+
headers.update(extra_headers)
68+
return Response(content=body, status_code=status, media_type="application/json", headers=headers)
69+
70+
71+
# ── Routes ───────────────────────────────────────────────────────────
72+
@app.get("/pipeline")
73+
async def pipeline():
74+
return _text(b"ok")
75+
76+
77+
@app.api_route("/baseline11", methods=["GET", "POST"])
78+
async def baseline11(request: Request):
79+
total = 0
80+
for v in request.query_params.values():
81+
try:
82+
total += int(v)
83+
except ValueError:
84+
pass
85+
if request.method == "POST":
86+
body = await request.body()
87+
if body:
88+
try:
89+
total += int(body.strip())
90+
except ValueError:
91+
pass
92+
return _text(str(total))
93+
94+
95+
@app.get("/baseline2")
96+
async def baseline2(request: Request):
97+
total = 0
98+
for v in request.query_params.values():
99+
try:
100+
total += int(v)
101+
except ValueError:
102+
pass
103+
return _text(str(total))
104+
105+
106+
@app.get("/json")
107+
async def json_endpoint():
108+
if dataset_items is None:
109+
return _text("No dataset", 500)
110+
items = []
111+
for d in dataset_items:
112+
item = dict(d)
113+
item["total"] = round(d["price"] * d["quantity"] * 100) / 100
114+
items.append(item)
115+
body = orjson.dumps({"items": items, "count": len(items)})
116+
return _json_resp(body)
117+
118+
119+
@app.get("/compression")
120+
async def compression_endpoint():
121+
if large_json_buf is None:
122+
return _text("No dataset", 500)
123+
compressed = gzip.compress(large_json_buf, compresslevel=1)
124+
return _json_resp(compressed, extra_headers={"Content-Encoding": "gzip"})
125+
126+
127+
@app.get("/db")
128+
async def db_endpoint(request: Request):
129+
if not db_available:
130+
return _json_resp(b'{"items":[],"count":0}')
131+
min_val = float(request.query_params.get("min", 10))
132+
max_val = float(request.query_params.get("max", 50))
133+
conn = _get_db()
134+
rows = conn.execute(DB_QUERY, (min_val, max_val)).fetchall()
135+
items = []
136+
for r in rows:
137+
items.append(
138+
{
139+
"id": r["id"],
140+
"name": r["name"],
141+
"category": r["category"],
142+
"price": r["price"],
143+
"quantity": r["quantity"],
144+
"active": bool(r["active"]),
145+
"tags": json.loads(r["tags"]),
146+
"rating": {"score": r["rating_score"], "count": r["rating_count"]},
147+
}
148+
)
149+
body = orjson.dumps({"items": items, "count": len(items)})
150+
return _json_resp(body)
151+
152+
153+
@app.post("/upload")
154+
async def upload_endpoint(request: Request):
155+
data = await request.body()
156+
return _text(str(len(data)))
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import os
2+
3+
bind = "0.0.0.0:8080"
4+
workers = len(os.sched_getaffinity(0)) * 2
5+
worker_class = "uvicorn.workers.UvicornWorker"
6+
keepalive = 120

frameworks/fastapi/meta.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"display_name": "FastAPI",
3+
"language": "Python",
4+
"type": "framework",
5+
"engine": "uvicorn",
6+
"description": "FastAPI async web framework on Uvicorn (uvloop), multi-worker via gunicorn.",
7+
"repo": "https://github.com/fastapi/fastapi",
8+
"enabled": true,
9+
"tests": [
10+
"baseline",
11+
"pipelined",
12+
"noisy",
13+
"limited-conn",
14+
"json",
15+
"upload",
16+
"compression",
17+
"mixed"
18+
]
19+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
fastapi==0.115.12
2+
uvicorn[standard]==0.34.0
3+
gunicorn==23.0.0
4+
uvloop==0.21.0
5+
aiosqlite==0.21.0
6+
orjson==3.10.15

0 commit comments

Comments
 (0)