Skip to content

Commit 5139ec0

Browse files
Update asgi templates (#1243)
* update FastAPI template * update BlackSheep template * update Litestar template * update Esmerald template * update Quart template * update Falcon template because PiccoloCRUD is not working properly * update Sanic template * status code has been changed from 204 to 200 because Quart and Sanic do not work well on the delete endpoint with no content * removed redundant tags from Esmerald's single task endpoint * updating docs for supported frameworks * sort imports --------- Co-authored-by: Daniel Townsend <[email protected]>
1 parent a4870e7 commit 5139ec0

File tree

8 files changed

+328
-121
lines changed

8 files changed

+328
-121
lines changed

docs/src/index.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,10 @@ Give me an ASGI web app!
6060
6161
piccolo asgi new
6262
63-
FastAPI, Starlette, BlackSheep, Litestar, Esmerald and Lilya are currently supported,
64-
with more coming soon.
63+
FastAPI, Starlette, BlackSheep, Litestar, Esmerald, Lilya, Quart, Falcon and Sanic
64+
are currently supported, with more coming soon.
6565

66-
-------------------------------------------------------------------------------
66+
----------------------------------------------------------------------------------
6767

6868
Videos
6969
------

piccolo/apps/asgi/commands/templates/app/_blacksheep_app.py.jinja

Lines changed: 57 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
import typing as t
1+
from typing import Any
22

3+
from blacksheep.exceptions import HTTPException
34
from blacksheep.server import Application
45
from blacksheep.server.bindings import FromJSON
5-
from blacksheep.server.openapi.v3 import OpenAPIHandler
6-
from blacksheep.server.responses import json
7-
from openapidocs.v3 import Info
6+
from blacksheep.server.openapi.v3 import OpenAPIHandler, Info
87
from piccolo.engine import engine_finder
98
from piccolo_admin.endpoints import create_admin
109
from piccolo_api.crud.serializers import create_pydantic_model
@@ -34,68 +33,97 @@ app.serve_files("static", root_path="/static")
3433
app.router.add_get("/", home)
3534

3635

37-
TaskModelIn: t.Any = create_pydantic_model(table=Task, model_name="TaskModelIn")
38-
TaskModelOut: t.Any = create_pydantic_model(
39-
table=Task, include_default_columns=True, model_name="TaskModelOut"
36+
TaskModelIn: Any = create_pydantic_model(
37+
table=Task,
38+
model_name="TaskModelIn",
4039
)
41-
TaskModelPartial: t.Any = create_pydantic_model(
42-
table=Task, model_name="TaskModelPartial", all_optional=True
40+
TaskModelOut: Any = create_pydantic_model(
41+
table=Task,
42+
include_default_columns=True,
43+
model_name="TaskModelOut",
4344
)
45+
TaskModelPartial: Any = (
46+
create_pydantic_model(
47+
table=Task,
48+
model_name="TaskModelPartial",
49+
all_optional=True,
50+
),
51+
)
52+
53+
54+
# Check if the record is None. Use for query callback
55+
def check_record_not_found(result: dict[str, Any]) -> dict[str, Any]:
56+
if result is None:
57+
raise HTTPException(status=404)
58+
return result
4459

4560

4661
@app.router.get("/tasks/")
47-
async def tasks() -> t.List[TaskModelOut]:
48-
return await Task.select().order_by(Task._meta.primary_key, ascending=False)
62+
async def tasks() -> list[TaskModelOut]:
63+
tasks = await Task.select().order_by(Task._meta.primary_key, ascending=False)
64+
return [TaskModelOut(**task) for task in tasks]
65+
66+
67+
@app.router.get("/tasks/{task_id}/")
68+
async def single_task(task_id: int) -> TaskModelOut:
69+
task = (
70+
await Task.select()
71+
.where(Task._meta.primary_key == task_id)
72+
.first()
73+
.callback(check_record_not_found)
74+
)
75+
return TaskModelOut(**task)
4976

5077

5178
@app.router.post("/tasks/")
5279
async def create_task(task_model: FromJSON[TaskModelIn]) -> TaskModelOut:
53-
task = Task(**task_model.value.dict())
80+
task = Task(**task_model.value.model_dump())
5481
await task.save()
5582
return TaskModelOut(**task.to_dict())
5683

5784

5885
@app.router.put("/tasks/{task_id}/")
5986
async def put_task(task_id: int, task_model: FromJSON[TaskModelIn]) -> TaskModelOut:
60-
task = await Task.objects().get(Task._meta.primary_key == task_id)
61-
if not task:
62-
return json({}, status=404)
87+
task = (
88+
await Task.objects()
89+
.get(Task._meta.primary_key == task_id)
90+
.callback(check_record_not_found)
91+
)
6392

64-
for key, value in task_model.value.dict().items():
93+
for key, value in task_model.value.model_dump().items():
6594
setattr(task, key, value)
6695

6796
await task.save()
68-
6997
return TaskModelOut(**task.to_dict())
7098

7199

72100
@app.router.patch("/tasks/{task_id}/")
73101
async def patch_task(
74102
task_id: int, task_model: FromJSON[TaskModelPartial]
75103
) -> TaskModelOut:
76-
task = await Task.objects().get(Task._meta.primary_key == task_id)
77-
if not task:
78-
return json({}, status=404)
104+
task = (
105+
await Task.objects()
106+
.get(Task._meta.primary_key == task_id)
107+
.callback(check_record_not_found)
108+
)
79109

80-
for key, value in task_model.value.dict().items():
110+
for key, value in task_model.value.model_dump().items():
81111
if value is not None:
82112
setattr(task, key, value)
83113

84114
await task.save()
85-
86115
return TaskModelOut(**task.to_dict())
87116

88117

89118
@app.router.delete("/tasks/{task_id}/")
90-
async def delete_task(task_id: int):
91-
task = await Task.objects().get(Task._meta.primary_key == task_id)
92-
if not task:
93-
return json({}, status=404)
94-
119+
async def delete_task(task_id: int) -> None:
120+
task = (
121+
await Task.objects()
122+
.get(Task._meta.primary_key == task_id)
123+
.callback(check_record_not_found)
124+
)
95125
await task.remove()
96126

97-
return json({})
98-
99127

100128
async def open_database_connection_pool(application):
101129
try:

piccolo/apps/asgi/commands/templates/app/_esmerald_app.py.jinja

Lines changed: 48 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import typing as t
1+
from typing import Any
22
from pathlib import Path
33

44
from esmerald import (
55
APIView,
66
Esmerald,
77
Gateway,
8+
HTTPException,
89
Include,
9-
JSONResponse,
1010
delete,
1111
get,
1212
post,
@@ -38,45 +38,72 @@ async def close_database_connection_pool():
3838
print("Unable to connect to the database")
3939

4040

41-
TaskModelIn: t.Any = create_pydantic_model(table=Task, model_name="TaskModelIn")
42-
TaskModelOut: t.Any = create_pydantic_model(
43-
table=Task, include_default_columns=True, model_name="TaskModelOut"
41+
TaskModelIn: Any = create_pydantic_model(
42+
table=Task,
43+
model_name="TaskModelIn",
4444
)
45+
TaskModelOut: Any = create_pydantic_model(
46+
table=Task,
47+
include_default_columns=True,
48+
model_name="TaskModelOut",
49+
)
50+
51+
52+
# Check if the record is None. Use for query callback
53+
def check_record_not_found(result: dict[str, Any]) -> dict[str, Any]:
54+
if result is None:
55+
raise HTTPException(
56+
detail="Record not found",
57+
status_code=404,
58+
)
59+
return result
4560

4661

4762
class TaskAPIView(APIView):
4863
path: str = "/"
49-
tags: str = ["Task"]
64+
tags: list[str] = ["Task"]
5065

5166
@get("/")
52-
async def tasks(self) -> t.List[TaskModelOut]:
53-
return await Task.select().order_by(Task._meta.primary_key, ascending=False)
67+
async def tasks(self) -> list[TaskModelOut]:
68+
tasks = await Task.select().order_by(Task._meta.primary_key, ascending=False)
69+
return [TaskModelOut(**task) for task in tasks]
70+
71+
@get("/{task_id}")
72+
async def single_task(self, task_id: int) -> TaskModelOut:
73+
task = (
74+
await Task.select()
75+
.where(Task._meta.primary_key == task_id)
76+
.first()
77+
.callback(check_record_not_found)
78+
)
79+
return TaskModelOut(**task)
5480

5581
@post("/")
5682
async def create_task(self, payload: TaskModelIn) -> TaskModelOut:
57-
task = Task(**payload.dict())
83+
task = Task(**payload.model_dump())
5884
await task.save()
59-
return task.to_dict()
85+
return TaskModelOut(**task.to_dict())
6086

6187
@put("/{task_id}")
6288
async def update_task(self, payload: TaskModelIn, task_id: int) -> TaskModelOut:
63-
task = await Task.objects().get(Task._meta.primary_key == task_id)
64-
if not task:
65-
return JSONResponse({}, status_code=404)
66-
67-
for key, value in payload.dict().items():
89+
task = (
90+
await Task.objects()
91+
.get(Task._meta.primary_key == task_id)
92+
.callback(check_record_not_found)
93+
)
94+
for key, value in payload.model_dump().items():
6895
setattr(task, key, value)
6996

7097
await task.save()
71-
72-
return task.to_dict()
98+
return TaskModelOut(**task.to_dict())
7399

74100
@delete("/{task_id}")
75101
async def delete_task(self, task_id: int) -> None:
76-
task = await Task.objects().get(Task._meta.primary_key == task_id)
77-
if not task:
78-
return JSONResponse({}, status_code=404)
79-
102+
task = (
103+
await Task.objects()
104+
.get(Task._meta.primary_key == task_id)
105+
.callback(check_record_not_found)
106+
)
80107
await task.remove()
81108

82109

piccolo/apps/asgi/commands/templates/app/_falcon_app.py.jinja

Lines changed: 63 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import os
2-
import typing as t
2+
from typing import Any
33

44
import falcon.asgi
55
from hypercorn.middleware import DispatcherMiddleware
66
from piccolo.engine import engine_finder
77
from piccolo_admin.endpoints import create_admin
8-
from piccolo_api.crud.endpoints import PiccoloCRUD
98

109
from home.endpoints import HomeEndpoint
1110
from home.piccolo_app import APP_CONFIG
@@ -28,33 +27,89 @@ async def close_database_connection_pool():
2827
print("Unable to connect to the database")
2928

3029

30+
# Check if the record is None. Use for query callback
31+
def check_record_not_found(result: dict[str, Any]) -> dict[str, Any]:
32+
if result is None:
33+
raise falcon.HTTPNotFound()
34+
return result
35+
36+
3137
class LifespanMiddleware:
3238
async def process_startup(
33-
self, scope: t.Dict[str, t.Any], event: t.Dict[str, t.Any]
39+
self, scope: dict[str, Any], event: dict[str, Any]
3440
) -> None:
3541
await open_database_connection_pool()
3642

3743
async def process_shutdown(
38-
self, scope: t.Dict[str, t.Any], event: t.Dict[str, t.Any]
44+
self, scope: dict[str, Any], event: dict[str, Any]
3945
) -> None:
4046
await close_database_connection_pool()
4147

4248

43-
app: t.Any = falcon.asgi.App(middleware=LifespanMiddleware())
49+
class TaskCollectionResource:
50+
async def on_get(self, req, resp):
51+
tasks = await Task.select().order_by(Task._meta.primary_key, ascending=False)
52+
resp.media = tasks
53+
54+
async def on_post(self, req, resp):
55+
data = await req.media
56+
task = Task(**data)
57+
await task.save()
58+
resp.status = falcon.HTTP_201
59+
resp.media = task.to_dict()
60+
61+
62+
class TaskItemResource:
63+
async def on_get(self, req, resp, task_id):
64+
task = (
65+
await Task.select()
66+
.where(Task._meta.primary_key == task_id)
67+
.first()
68+
.callback(check_record_not_found)
69+
)
70+
resp.status = falcon.HTTP_200
71+
resp.media = task
72+
73+
async def on_put(self, req, resp, task_id):
74+
task = (
75+
await Task.objects()
76+
.get(Task._meta.primary_key == task_id)
77+
.callback(check_record_not_found)
78+
)
79+
80+
data = await req.media
81+
for key, value in data.items():
82+
setattr(task, key, value)
83+
84+
await task.save()
85+
resp.status = falcon.HTTP_200
86+
resp.media = task.to_dict()
87+
88+
async def on_delete(self, req, resp, task_id):
89+
task = (
90+
await Task.objects()
91+
.get(Task._meta.primary_key == task_id)
92+
.callback(check_record_not_found)
93+
)
94+
resp.status = falcon.HTTP_204
95+
await task.remove()
96+
97+
98+
app: Any = falcon.asgi.App(middleware=LifespanMiddleware())
4499
app.add_static_route("/static", directory=os.path.abspath("static"))
45100
app.add_route("/", HomeEndpoint())
101+
app.add_route("/tasks/", TaskCollectionResource())
102+
app.add_route("/tasks/{task_id:int}", TaskItemResource())
46103

47-
PICCOLO_CRUD: t.Any = PiccoloCRUD(table=Task)
48104

49-
# enable the Admin and PiccoloCrud app using DispatcherMiddleware
105+
# enable the admin application using DispatcherMiddleware
50106
app = DispatcherMiddleware( # type: ignore
51107
{
52108
"/admin": create_admin(
53109
tables=APP_CONFIG.table_classes,
54110
# Required when running under HTTPS:
55111
# allowed_hosts=['my_site.com']
56112
),
57-
"/tasks": PICCOLO_CRUD,
58113
"": app,
59114
}
60115
)

0 commit comments

Comments
 (0)