Skip to content

Commit 3f42459

Browse files
committed
Added TornadoRouter
1 parent b47b56f commit 3f42459

File tree

21 files changed

+537
-5
lines changed

21 files changed

+537
-5
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ FastOpenAPI follows the [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
88

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

1213
## [0.3.1] - 2025-03-15
1314

README.md

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
<b>FastOpenAPI</b> is a library for generating and integrating OpenAPI schemas using Pydantic and various frameworks.
77
</p>
88

9+
<p align="center">
10+
This project was inspired by <a href="https://fastapi.tiangolo.com/">FastAPI</a> and aims to provide a similar developer-friendly experience.
11+
</p>
12+
913
<p align="center">
1014
<img src="https://img.shields.io/github/license/mr-fatalyst/fastopenapi">
1115
<img src="https://github.com/mr-fatalyst/fastopenapi/actions/workflows/master.yml/badge.svg">
@@ -37,6 +41,9 @@ pip install fastopenapi[sanic]
3741
```bash
3842
pip install fastopenapi[starlette]
3943
```
44+
```bash
45+
pip install fastopenapi[tornado]
46+
```
4047

4148
---
4249

@@ -80,7 +87,7 @@ pip install fastopenapi[starlette]
8087
```
8188
</details>
8289

83-
- ![Flask](https://img.shields.io/badge/-Flask-000000?style=flat&logo=flask&logoColor=white)
90+
- ![Flask](https://img.shields.io/badge/-Flask-EEEEEE?style=flat&logo=flask&logoColor=black)
8491
<details>
8592
<summary>Click to expand the Flask Example</summary>
8693

@@ -167,7 +174,7 @@ pip install fastopenapi[starlette]
167174
```
168175
</details>
169176

170-
- ![Starlette](https://img.shields.io/badge/-Starlette-4B0082?style=flat&logo=fastapi&logoColor=white)
177+
- ![Starlette](https://img.shields.io/badge/-Starlette-4B0082?style=flat&logo=python&logoColor=white)
171178
<details>
172179
<summary>Click to expand the Starlette Example</summary>
173180

@@ -196,6 +203,45 @@ pip install fastopenapi[starlette]
196203
```
197204
</details>
198205

206+
- ![Tornado](https://img.shields.io/badge/-Tornado-2980B9?style=flat&logo=python&logoColor=white)
207+
<details>
208+
<summary>Click to expand the Tornado Example</summary>
209+
210+
```python
211+
import asyncio
212+
213+
from pydantic import BaseModel
214+
from tornado.web import Application
215+
216+
from fastopenapi.routers.tornado import TornadoRouter
217+
218+
app = Application()
219+
220+
router = TornadoRouter(
221+
app=app, docs_url="/docs", openapi_url="/openapi.json", openapi_version="3.0.0"
222+
)
223+
224+
225+
class HelloResponse(BaseModel):
226+
message: str
227+
228+
229+
@router.get("/hello", tags=["Hello"], status_code=200, response_model=HelloResponse)
230+
def hello(name: str):
231+
"""Say hello from Tornado"""
232+
return HelloResponse(message=f"Hello, {name}! It's Tornado!")
233+
234+
235+
async def main():
236+
app.listen(8000)
237+
await asyncio.Event().wait()
238+
239+
240+
if __name__ == "__main__":
241+
asyncio.run(main())
242+
```
243+
</details>
244+
199245
### Step 2. Run the server
200246

201247
Launch the application:
@@ -206,16 +252,21 @@ python main.py
206252

207253
Once launched, the documentation will be available at:
208254

255+
Swagger UI:
256+
```
257+
http://127.0.0.1:8000/docs
258+
```
259+
ReDoc UI:
209260
```
210-
http://127.0.0.1:8000/docs/
261+
http://127.0.0.1:8000/redoc
211262
```
212263

213264
---
214265

215266
## ⚙️ Features
216267
- **Generate OpenAPI schemas** with Pydantic v2.
217268
- **Data validation** using Pydantic models.
218-
- **Supports multiple frameworks:** Falcon, Flask, Quart, Sanic, Starlette.
269+
- **Supports multiple frameworks:** Falcon, Flask, Quart, Sanic, Starlette, Tornado.
219270
- **Proxy routing provides FastAPI-style routing**
220271

221272
---

examples/tornado/app/__init__.py

Whitespace-only changes.

examples/tornado/app/api/__init__.py

Whitespace-only changes.

examples/tornado/app/api/routes.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from fastopenapi.routers import TornadoRouter
2+
3+
from .v1 import v1_router
4+
5+
api_router = TornadoRouter()
6+
api_router.include_router(v1_router, prefix="/v1")
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from fastopenapi.routers import TornadoRouter
2+
3+
from .authors import router as authors_router
4+
from .posts import router as posts_router
5+
6+
v1_router = TornadoRouter()
7+
v1_router.include_router(authors_router)
8+
v1_router.include_router(posts_router)
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
from tornado.web import HTTPError
2+
3+
from fastopenapi.routers import TornadoRouter
4+
5+
from ...schemas.authors import (
6+
AuthorSchema,
7+
CreateAuthorSchema,
8+
FilterAuthorSchema,
9+
UpdateAuthorSchema,
10+
)
11+
from ...services.authors import AuthorService
12+
13+
author_service = AuthorService()
14+
15+
router = TornadoRouter()
16+
17+
18+
@router.post("/authors", tags=["Authors"], status_code=201, response_model=AuthorSchema)
19+
async def create_author(body: CreateAuthorSchema) -> AuthorSchema:
20+
return await author_service.create_author(body)
21+
22+
23+
@router.get("/authors/{author_id}", tags=["Authors"], response_model=AuthorSchema)
24+
async def get_author(author_id: int) -> AuthorSchema:
25+
author = await author_service.get_author(author_id)
26+
if not author:
27+
raise HTTPError(status_code=404, log_message="Not Found")
28+
return author
29+
30+
31+
@router.get("/authors/", tags=["Authors"], response_model=list[AuthorSchema])
32+
async def get_authors(body: FilterAuthorSchema) -> list[AuthorSchema]:
33+
return await author_service.get_authors(body)
34+
35+
36+
@router.delete("/authors/{author_id}", tags=["Authors"], status_code=204)
37+
async def delete_author(author_id: int) -> None:
38+
author = await author_service.delete_author(author_id)
39+
if not author:
40+
raise HTTPError(status_code=404, log_message="Not Found")
41+
42+
43+
@router.patch("/authors/{author_id}", tags=["Authors"], response_model=AuthorSchema)
44+
async def update_author(author_id: int, body: UpdateAuthorSchema) -> AuthorSchema:
45+
author = await author_service.update_author(author_id, body)
46+
if not author:
47+
raise HTTPError(status_code=404, log_message="Not Found")
48+
return author
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
from tornado.web import HTTPError
2+
3+
from fastopenapi.routers import TornadoRouter
4+
5+
from ...schemas.posts import (
6+
CreatePostSchema,
7+
FilterPostSchema,
8+
PostSchema,
9+
UpdatePostSchema,
10+
)
11+
from ...services.posts import PostService
12+
13+
post_service = PostService()
14+
router = TornadoRouter()
15+
16+
17+
@router.post("/posts", tags=["Posts"], status_code=201, response_model=PostSchema)
18+
async def create_post(body: CreatePostSchema) -> PostSchema:
19+
return await post_service.create_post(body)
20+
21+
22+
@router.get("/posts/{post_id}", tags=["Posts"], response_model=PostSchema)
23+
async def get_post(post_id: int) -> PostSchema:
24+
post = await post_service.get_post(post_id)
25+
if not post:
26+
raise HTTPError(status_code=404, log_message="Not Found")
27+
return post
28+
29+
30+
@router.get("/posts/", tags=["Posts"], response_model=list[PostSchema])
31+
async def get_posts(body: FilterPostSchema) -> list[PostSchema]:
32+
return await post_service.get_posts(body)
33+
34+
35+
@router.delete("/posts/{post_id}", tags=["Posts"], status_code=204)
36+
async def delete_post(post_id: int) -> None:
37+
post = await post_service.delete_post(post_id)
38+
if not post:
39+
raise HTTPError(status_code=404, log_message="Not Found")
40+
41+
42+
@router.patch("/posts/{post_id}", tags=["Posts"], response_model=PostSchema)
43+
async def update_post(post_id: int, body: UpdatePostSchema) -> PostSchema:
44+
post = await post_service.update_post(post_id, body)
45+
if not post:
46+
raise HTTPError(status_code=404, log_message="Not Found")
47+
return post

examples/tornado/app/schemas/__init__.py

Whitespace-only changes.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from pydantic import BaseModel, Field
2+
3+
4+
class AuthorSchema(BaseModel):
5+
id: int
6+
name: str
7+
bio: str | None = None
8+
9+
10+
class FilterAuthorSchema(BaseModel):
11+
id: int = Field(default=None)
12+
name: str = Field(default=None)
13+
14+
15+
class CreateAuthorSchema(BaseModel):
16+
name: str = Field(..., max_length=50)
17+
bio: str | None = Field(None, max_length=200)
18+
19+
20+
class UpdateAuthorSchema(BaseModel):
21+
name: str = Field(default=None, max_length=50)
22+
bio: str = Field(default=None, max_length=200)

0 commit comments

Comments
 (0)