Skip to content

Commit e6d1c76

Browse files
committed
Example rendering AsyncAPI from pydantic models and mapping
1 parent 2fa6ccd commit e6d1c76

File tree

5 files changed

+180
-110
lines changed

5 files changed

+180
-110
lines changed

src/http_app/routes/__init__.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,24 @@
11
from fastapi import FastAPI
22

3-
from http_app.routes import api, events, graphql, hello, ping, user_registered_hook, asyncapi_docs
4-
from . import websocket
3+
from http_app.routes import (
4+
api,
5+
docs_ws,
6+
events,
7+
graphql,
8+
hello,
9+
ping,
10+
user_registered_hook,
11+
)
12+
13+
from . import ws
514

615

716
def init_routes(app: FastAPI) -> None:
817
app.include_router(api.router)
9-
app.include_router(asyncapi_docs.router)
18+
app.include_router(docs_ws.router)
1019
app.include_router(ping.router)
1120
app.include_router(hello.router)
1221
app.include_router(events.router)
1322
app.include_router(user_registered_hook.router)
1423
app.include_router(graphql.router, prefix="/graphql")
15-
app.include_router(websocket.router)
24+
app.include_router(ws.router)

src/http_app/routes/asyncapi_docs.py

Lines changed: 0 additions & 104 deletions
This file was deleted.

src/http_app/routes/docs_ws.py

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
import json
2+
3+
import pydantic_asyncapi as pa
4+
from fastapi import APIRouter
5+
from starlette.responses import HTMLResponse
6+
7+
from domains.books.events import BookCreatedV1
8+
9+
message_map = {
10+
"chat_channel": [BookCreatedV1]
11+
}
12+
13+
components_schemas = {}
14+
channel_messages = {}
15+
16+
# Prepare some data from a map
17+
for channel, messages in message_map.items():
18+
channel_messages[channel] = {}
19+
for message in messages:
20+
components_schemas[message.__name__] = message.model_json_schema(ref_template="#/components/schemas/{model}")
21+
components_schemas.update(message.model_json_schema(ref_template="#/components/schemas/{model}")["$defs"])
22+
channel_messages[channel][message.__name__] = pa.v3.Message(
23+
payload=pa.v3.Reference(ref=f"#/components/schemas/{message.__name__}")
24+
)
25+
26+
27+
schema = pa.AsyncAPIV3(
28+
asyncapi="3.0.0",
29+
info=pa.v3.Info(
30+
title="Bookstore API",
31+
version="1.0.0",
32+
description="A bookstore aysncapi specification",
33+
),
34+
components=pa.v3.Components(
35+
schemas=components_schemas,
36+
),
37+
servers={
38+
"chat": pa.v3.Server(
39+
host="localhost",
40+
protocol="websocket",
41+
)
42+
},
43+
channels={
44+
"chat_channel": pa.v3.Channel(
45+
title="chat_channel_title",
46+
servers=[pa.v3.Reference(ref="#/servers/chat")],
47+
messages=channel_messages["chat_channel"],
48+
)
49+
},
50+
operations={
51+
"chat_operation": pa.v3.Operation(
52+
action="receive",
53+
channel=pa.v3.Reference(ref="#/channels/chat_channel"),
54+
)
55+
},
56+
57+
)
58+
59+
router = APIRouter(prefix="/docs/ws")
60+
61+
62+
@router.get(
63+
"/asyncapi.json",
64+
response_model_exclude_unset=True,
65+
include_in_schema=False,
66+
)
67+
def asyncapi_raw() -> pa.AsyncAPIV3:
68+
return schema
69+
70+
71+
ASYNCAPI_COMPONENT_VERSION = "latest"
72+
73+
ASYNCAPI_JS_DEFAULT_URL = (
74+
f"https://unpkg.com/@asyncapi/react-component@{ASYNCAPI_COMPONENT_VERSION}/browser/standalone/index.js"
75+
)
76+
NORMALIZE_CSS_DEFAULT_URL = (
77+
"https://cdn.jsdelivr.net/npm/modern-normalize/modern-normalize.min.css"
78+
)
79+
ASYNCAPI_CSS_DEFAULT_URL = (
80+
f"https://unpkg.com/@asyncapi/react-component@{ASYNCAPI_COMPONENT_VERSION}/styles/default.min.css"
81+
)
82+
83+
84+
# https://github.com/asyncapi/asyncapi-react/blob/v2.5.0/docs/usage/standalone-bundle.md
85+
@router.get("", include_in_schema=False)
86+
def get_asyncapi_html(
87+
sidebar: bool = True,
88+
info: bool = True,
89+
servers: bool = True,
90+
operations: bool = True,
91+
messages: bool = True,
92+
schemas: bool = True,
93+
errors: bool = True,
94+
expand_message_examples: bool = True,
95+
title: str = "Websocket",
96+
) -> HTMLResponse:
97+
98+
"""Generate HTML for displaying an AsyncAPI document."""
99+
config = {
100+
# "schema": schema_json,
101+
"schema": {
102+
"url": "/docs/ws/asyncapi.json",
103+
},
104+
"config": {
105+
"show": {
106+
"sidebar": sidebar,
107+
"info": info,
108+
"servers": servers,
109+
"operations": operations,
110+
"messages": messages,
111+
"schemas": schemas,
112+
"errors": errors,
113+
},
114+
"expand": {
115+
"messageExamples": expand_message_examples,
116+
},
117+
"sidebar": {
118+
"showServers": "byDefault",
119+
"showOperations": "byDefault",
120+
},
121+
},
122+
}
123+
124+
return HTMLResponse(
125+
"""
126+
<!DOCTYPE html>
127+
<html>
128+
<head>
129+
"""
130+
f"""
131+
<title>{title} AsyncAPI</title>
132+
"""
133+
"""
134+
<link rel="icon" href="https://www.asyncapi.com/favicon.ico">
135+
<link rel="icon" type="image/png" sizes="16x16" href="https://www.asyncapi.com/favicon-16x16.png">
136+
<link rel="icon" type="image/png" sizes="32x32" href="https://www.asyncapi.com/favicon-32x32.png">
137+
<link rel="icon" type="image/png" sizes="194x194" href="https://www.asyncapi.com/favicon-194x194.png">
138+
"""
139+
f"""
140+
<link rel="stylesheet" href="{NORMALIZE_CSS_DEFAULT_URL}">
141+
<link rel="stylesheet" href="{ASYNCAPI_CSS_DEFAULT_URL}">
142+
"""
143+
"""
144+
</head>
145+
146+
147+
<body>
148+
<div id="asyncapi"></div>
149+
"""
150+
f"""
151+
<script src="{ASYNCAPI_JS_DEFAULT_URL}"></script>
152+
<script>
153+
"""
154+
f"""
155+
AsyncApiStandalone.render(
156+
{json.dumps(config)},
157+
document.getElementById('asyncapi')
158+
);
159+
"""
160+
"""
161+
</script>
162+
</body>
163+
</html>
164+
"""
165+
)

src/http_app/routes/websocket/__init__.py renamed to src/http_app/routes/ws/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
from . import chat
44

55
router = APIRouter(prefix="/ws")
6-
router.include_router(chat.router)
6+
router.include_router(chat.router)

src/http_app/routes/websocket/chat.py renamed to src/http_app/routes/ws/chat.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,4 @@ async def websocket_endpoint(websocket: WebSocket, client_id: int):
3636
await manager.broadcast(f"Client #{client_id} says: {data}")
3737
except WebSocketDisconnect:
3838
manager.disconnect(websocket)
39-
await manager.broadcast(f"Client #{client_id} left the chat")
39+
await manager.broadcast(f"Client #{client_id} left the chat")

0 commit comments

Comments
 (0)