Skip to content

Commit 4e8e6df

Browse files
committed
Added benchmark for Tornado. Removed add_docs_route and add_openapi_route.
1 parent 8abce23 commit 4e8e6df

File tree

29 files changed

+454
-44
lines changed

29 files changed

+454
-44
lines changed

CHANGELOG.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,21 @@ All notable changes to FastOpenAPI are documented in this file.
44

55
FastOpenAPI follows the [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) format.
66

7-
## [0.4.0] - Unreleased
7+
## [0.4.0] - 2025-03-20
88

99
### Added
1010
- `ReDoc UI` and default URL (`host:port/redoc`)
1111
- `TornadoRouter` for integration with the `Tornado` framework
1212

13+
### Changed
14+
- Revised and updated all tests.
15+
16+
### Fixed
17+
- Status code for error response fixed: 422 -> 500
18+
19+
### Removed
20+
- Removed the `add_docs_route` and `add_openapi_route` from `BaseRouter`.
21+
1322
## [0.3.1] - 2025-03-15
1423

1524
### Fixed

README.md

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ pip install fastopenapi[tornado]
6969
from fastopenapi.routers import FalconRouter
7070

7171
app = falcon.asgi.App()
72-
router = FalconRouter(app=app, docs_url="/docs/", openapi_version="3.0.0")
72+
router = FalconRouter(app=app)
7373

7474

7575
class HelloResponse(BaseModel):
@@ -98,7 +98,7 @@ pip install fastopenapi[tornado]
9898
from fastopenapi.routers import FlaskRouter
9999

100100
app = Flask(__name__)
101-
router = FlaskRouter(app=app, docs_url="/docs/", openapi_version="3.0.0")
101+
router = FlaskRouter(app=app)
102102

103103

104104
class HelloResponse(BaseModel):
@@ -127,7 +127,7 @@ pip install fastopenapi[tornado]
127127
from fastopenapi.routers import QuartRouter
128128

129129
app = Quart(__name__)
130-
router = QuartRouter(app=app, docs_url="/docs/", openapi_version="3.0.0")
130+
router = QuartRouter(app=app)
131131

132132

133133
class HelloResponse(BaseModel):
@@ -156,7 +156,7 @@ pip install fastopenapi[tornado]
156156
from fastopenapi.routers import SanicRouter
157157

158158
app = Sanic("MySanicApp")
159-
router = SanicRouter(app=app, docs_url="/docs/", openapi_version="3.0.0")
159+
router = SanicRouter(app=app)
160160

161161

162162
class HelloResponse(BaseModel):
@@ -186,7 +186,7 @@ pip install fastopenapi[tornado]
186186
from fastopenapi.routers import StarletteRouter
187187

188188
app = Starlette()
189-
router = StarletteRouter(app=app, docs_url="/docs/", openapi_version="3.0.0")
189+
router = StarletteRouter(app=app)
190190

191191

192192
class HelloResponse(BaseModel):
@@ -217,9 +217,7 @@ pip install fastopenapi[tornado]
217217

218218
app = Application()
219219

220-
router = TornadoRouter(
221-
app=app, docs_url="/docs", openapi_url="/openapi.json", openapi_version="3.0.0"
222-
)
220+
router = TornadoRouter(app=app)
223221

224222

225223
class HelloResponse(BaseModel):

benchmarks/README.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@ Each implementation runs in a separate instance, and the benchmark measures resp
88

99
### 📈 Rough results
1010
- You can check rough results here:
11-
- [Falcon](FALCON.md)
12-
- [Flask](FLASK.md)
13-
- [Quart](QUART.md)
14-
- [Sanic](SANIC.md)
15-
- [Starlette](STARLETTE.md)
11+
- [Falcon](falcon/FALCON.md)
12+
- [Flask](flask/FLASK.md)
13+
- [Quart](quart/QUART.md)
14+
- [Sanic](sanic/SANIC.md)
15+
- [Starlette](starlette/STARLETTE.md)
16+
- [Tornado](tornado/TORNADO.md)
1617

1718
### 📖 How It Works
1819
- The script runs **10,000 requests per endpoint**. You can set your own value.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

benchmarks/tornado/TORNADO.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Tornado Benchmark
2+
3+
---
4+
5+
## Testing Original Implementation
6+
```
7+
Original - Running 10000 iterations per endpoint
8+
--------------------------------------------------
9+
GET all records: 9.3412 sec total, 0.93 ms per request
10+
GET one record: 9.5527 sec total, 0.96 ms per request
11+
POST new record: 9.9068 sec total, 0.99 ms per request
12+
PUT record: 9.8596 sec total, 0.99 ms per request
13+
PATCH record: 9.8612 sec total, 0.99 ms per request
14+
DELETE record: 19.0970 sec total, 1.91 ms per request
15+
```
16+
---
17+
18+
## Testing FastOpenAPI Implementation
19+
20+
```
21+
FastOpenAPI - Running 10000 iterations per endpoint
22+
--------------------------------------------------
23+
GET all records: 9.5980 sec total, 0.96 ms per request
24+
GET one record: 9.9557 sec total, 1.00 ms per request
25+
POST new record: 10.2566 sec total, 1.03 ms per request
26+
PUT record: 10.4081 sec total, 1.04 ms per request
27+
PATCH record: 10.2608 sec total, 1.03 ms per request
28+
DELETE record: 19.9923 sec total, 2.00 ms per request
29+
```
30+
31+
---
32+
33+
## Performance Comparison (10000 iterations)
34+
35+
| Endpoint | Original | FastOpenAPI | Difference |
36+
|--------------------------|----------|-------------|------------|
37+
| GET all records | 0.93 ms | 0.96 ms | 0.03 ms (+2.7%) |
38+
| GET one record | 0.96 ms | 1.00 ms | 0.04 ms (+4.2%) |
39+
| POST new record | 0.99 ms | 1.03 ms | 0.03 ms (+3.5%) |
40+
| PUT record | 0.99 ms | 1.04 ms | 0.05 ms (+5.6%) |
41+
| PATCH record | 0.99 ms | 1.03 ms | 0.04 ms (+4.1%) |
42+
| DELETE record | 1.91 ms | 2.00 ms | 0.09 ms (+4.7%) |
43+
44+
---
45+
46+
[<< Back](README.md)
47+
48+
---
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import asyncio
2+
3+
from tornado.web import Application, HTTPError
4+
5+
from benchmarks.tornado.with_fastopenapi.schemas import (
6+
RecordCreate,
7+
RecordResponse,
8+
RecordUpdate,
9+
)
10+
from benchmarks.tornado.with_fastopenapi.storage import RecordStore
11+
from fastopenapi.routers import TornadoRouter
12+
13+
# Initialize Sanic app and router
14+
app = Application()
15+
router = TornadoRouter(
16+
app=app,
17+
title="Record API",
18+
description="A simple Record API built with FastOpenAPI and Tornado",
19+
version="1.0.0",
20+
docs_url="/docs",
21+
redoc_url="/redoc",
22+
openapi_url="/openapi.json",
23+
)
24+
25+
# Initialize the storage
26+
store = RecordStore()
27+
28+
29+
# Define routes using the TornadoRouter decorators
30+
@router.get("/records", tags=["records"], response_model=list[RecordResponse])
31+
async def get_records():
32+
"""
33+
Get all records
34+
"""
35+
return store.get_all()
36+
37+
38+
@router.get("/records/{record_id}", tags=["records"], response_model=RecordResponse)
39+
async def get_record(record_id: str):
40+
"""
41+
Get a specific record by ID
42+
"""
43+
record = store.get_by_id(record_id)
44+
if not record:
45+
raise HTTPError(status_code=404, log_message="Not Found")
46+
return record
47+
48+
49+
@router.post(
50+
"/records", tags=["records"], status_code=201, response_model=RecordResponse
51+
)
52+
async def create_record(record: RecordCreate):
53+
"""
54+
Create a new record
55+
"""
56+
return store.create(record)
57+
58+
59+
@router.put("/records/{record_id}", tags=["records"], response_model=RecordResponse)
60+
async def update_record_full(record_id: str, record: RecordCreate):
61+
"""
62+
Update a record completely (all fields required)
63+
"""
64+
existing_record = store.get_by_id(record_id)
65+
if not existing_record:
66+
raise HTTPError(status_code=404, log_message="Not Found")
67+
68+
# Delete and recreate with the same ID
69+
store.delete(record_id)
70+
new_record = {"id": record_id, **record.model_dump()}
71+
store.records[record_id] = new_record
72+
return RecordResponse(**new_record)
73+
74+
75+
@router.patch("/records/{record_id}", tags=["records"], response_model=RecordResponse)
76+
async def update_record_partial(record_id: str, record: RecordUpdate):
77+
"""
78+
Update a record partially (only specified fields)
79+
"""
80+
updated_record = store.update(record_id, record)
81+
if not updated_record:
82+
raise HTTPError(status_code=404, log_message="Not Found")
83+
return updated_record
84+
85+
86+
@router.delete("/records/{record_id}", tags=["records"], status_code=204)
87+
async def delete_record(record_id: str):
88+
"""
89+
Delete a record
90+
"""
91+
if not store.delete(record_id):
92+
raise HTTPError(status_code=404, log_message="Not Found")
93+
return None
94+
95+
96+
async def main():
97+
app.listen(8001)
98+
await asyncio.Event().wait()
99+
100+
101+
if __name__ == "__main__":
102+
asyncio.run(main())

0 commit comments

Comments
 (0)