Skip to content

Commit 446b58c

Browse files
vvlrffLancetnik
andauthored
feat: Add Try It Out feature for AsyncAPI documentation (#2777)
* feat: Add Try It Out feature for AsyncAPI documentation with endpoint registration (#2775) - Enhanced AsyncAPI documentation to include a Try It Out feature, allowing users to publish test messages directly from the browser UI. - Registered a POST endpoint at `{asyncapi_path}/try` for publishing messages in test mode. - Updated `AsgiFastStream` to accept a `try_it_out` parameter for enabling/disabling the feature. - Implemented `TryItOutProcessor` to handle message publishing and validation. - Added tests to verify the functionality of the Try It Out feature, including endpoint registration and message delivery. * style: Refactor code for better readability and formatting in AsyncAPIRoute and test cases * refactor: polish PR * tests: fix result validation * feat: Enhance Try It Out feature with structured message handling and updated documentation * style: Improve code formatting for message structure in AsyncAPI test cases * feat: return just a result * feat: Add asyncapi_js_react_url parameter to AsyncAPI functions - Introduced asyncapi_js_react_url parameter in make_asyncapi_asgi, AsyncAPIRoute, and get_asyncapi_html functions to support React-based rendering. - Updated logic to utilize asyncapi_js_react_url when the Try It Out feature is enabled, enhancing flexibility in AsyncAPI documentation rendering. * refactor: Simplify asyncapi_js_url assignment logic in get_asyncapi_html function * feat: Enhance AsyncAPI functionality with new parameters and improved Try It Out processing * fix: Update TryItOutProcessor to use set for empty check in JSONResponse * fix: Refine TryItOutProcessor response handling and enhance test coverage for various payload types * chore: bump version * docs: fix markup * docs: move TryItOut to AsyncAPI docs * refactor: use standalone TryItOut plugin * refactor: use unpkg AsyncAPI CDN * lint: apply changes --------- Co-authored-by: Nikita Pastukhov <diementros@yandex.ru> Co-authored-by: Pastukhov Nikita <nikita@pastukhov-dev.ru>
1 parent 1b95019 commit 446b58c

File tree

16 files changed

+960
-243
lines changed

16 files changed

+960
-243
lines changed

docs/docs/en/getting-started/asgi.md

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ If you want to write your own simple **HTTP**-endpoint, you can use our `#!pytho
9191

9292
You can also use helper functions to access query parameters and headers:
9393

94-
```python linenums="1" hl_lines="1 8-9 19"
94+
```python linenums="1" hl_lines="1 8-9 18"
9595
{! docs_src/getting_started/asgi/auth_app.py !}
9696
```
9797

@@ -106,18 +106,19 @@ Dependency Injection works with [**FastDepends**](https://lancetnik.github.io/Fa
106106

107107
By default, any ASGI routes will be added to your AsyncAPI documentation. If you wish to exclude these routes, just do the following:
108108

109-
```python linenums="1"
109+
```python linenums="1" hl_lines="5"
110110
app = AsgiFastStream(
111111
broker,
112-
asgi_routes=[
113-
("/health", make_ping_asgi(broker, timeout=5.0, include_in_schema=False)),
114-
]
112+
asgi_routes=[(
113+
"/health",
114+
make_ping_asgi(broker, timeout=5.0, include_in_schema=False)
115+
)]
115116
)
116117
```
117118

118119
Or, for custom ASGI routes:
119120

120-
```python linenums="1"
121+
```python linenums="1" hl_lines="1"
121122
@get(include_in_schema=False)
122123
async def liveness_ping(scope):
123124
return AsgiResponse(b"", status_code=200)
@@ -148,17 +149,20 @@ app = AsgiFastStream(
148149
)
149150
```
150151

151-
Now, your **AsyncAPI HTML** representation can be found by the `/docs` url.
152+
Now, your **AsyncAPI HTML** representation can be found by the `/docs/asyncapi` url.
153+
154+
!!! note
155+
For extended examples on the **AsyncAPI** feature, see [Serving the AsyncAPI Documentation](./asyncapi/hosting.md){.internal-link} page.
152156

153157
### FastStream Object Reuse
154158

155-
You may also use regular `FastStream` application object for similar result.
159+
You may also use regular `FastStream.as_asgi()` method for similar result.
156160

157-
```python linenums="1" hl_lines="2 12"
161+
```python linenums="1" hl_lines="1 12"
158162
from faststream import FastStream
159163
from faststream.nats import NatsBroker
160164
from faststream.specification import AsyncAPI
161-
from faststream.asgi import make_ping_asgi, AsgiResponse
165+
from faststream.asgi import make_ping_asgi, AsgiResponse, get
162166

163167
broker = NatsBroker()
164168

@@ -211,5 +215,5 @@ async def start_broker(app):
211215
app = FastAPI(lifespan=start_broker)
212216

213217
app.mount("/health", make_ping_asgi(broker, timeout=5.0))
214-
app.mount("/asyncapi", make_asyncapi_asgi(asyncapi))
218+
app.mount("/asyncapi", make_asyncapi_asgi(asyncapi, try_it_out=False))
215219
```

docs/docs/en/getting-started/asyncapi/hosting.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,59 @@ app = AsgiFastStream(
9595

9696
After running the script, the **AsyncAPI** docs will be available at: <http://localhost:8000/docs/asyncapi>
9797

98+
## Try It Out
99+
100+
The AsyncAPI documentation page includes a built-in **Try It Out** feature that lets you publish test messages directly from the browser UI, without leaving the docs page.
101+
102+
!!! note
103+
The Try It Out UI is powered by the [asyncapi-try-it-plugin](https://github.com/Shepard2154/asyncapi-try-it-plugin){.external-link target="_blank"} — a plugin for AsyncAPI that adds a Swagger-like interface to send test messages to message brokers. Thanks to the maintainers for this functionality.
104+
105+
By default, when you set `asyncapi_path`, a companion `POST` endpoint is automatically registered at `{asyncapi_path}/try`. The UI sends the message payload to this endpoint, which publishes it to your broker in test mode (without requiring a real broker connection).
106+
107+
```python linenums="1" hl_lines="7"
108+
from faststream.nats import NatsBroker
109+
from faststream.asgi import AsgiFastStream
110+
111+
broker = NatsBroker()
112+
113+
# POST /docs/asyncapi/try is registered automatically
114+
app = AsgiFastStream(broker, asyncapi_path="/docs/asyncapi")
115+
```
116+
117+
To disable the feature, use `AsyncAPIRoute` with `try_it_out=False`:
118+
119+
```python linenums="1" hl_lines="2 8"
120+
from faststream.nats import NatsBroker
121+
from faststream.asgi import AsgiFastStream, AsyncAPIRoute
122+
123+
broker = NatsBroker()
124+
125+
app = AsgiFastStream(
126+
broker,
127+
asyncapi_path=AsyncAPIRoute("/docs/asyncapi", try_it_out=False),
128+
)
129+
```
130+
131+
If you want to point the Try It Out UI to an **external backend** (e.g. a separate service or a production broker URL), pass a custom `try_it_out_url` via `AsyncAPIRoute`:
132+
133+
```python linenums="1" hl_lines="2 10"
134+
from faststream.nats import NatsBroker
135+
from faststream.asgi import AsgiFastStream, AsyncAPIRoute
136+
137+
broker = NatsBroker()
138+
139+
app = AsgiFastStream(
140+
broker,
141+
asyncapi_path=AsyncAPIRoute(
142+
"/docs/asyncapi",
143+
try_it_out_url="https://api.example.com/asyncapi/try",
144+
),
145+
)
146+
```
147+
148+
!!! note
149+
When `try_it_out_url` is set on `AsyncAPIRoute`, it overrides the URL the browser sends requests to. The local `POST {asyncapi_path}/try` endpoint is still registered and reachable regardless of `try_it_out_url`, unless you also pass `try_it_out=False`.
150+
98151
## Integration with Different HTTP Frameworks (**FastAPI** Example)
99152

100153
**FastStream** provides two robust approaches to combine your message broker documentation with any **ASGI** web frameworks.

faststream/_internal/testing/broker.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,12 +94,15 @@ async def _create_ctx(self) -> AsyncGenerator[Broker, None]:
9494

9595
with context:
9696
async with self.broker:
97+
saved_running = {sub: sub.running for sub in self.broker.subscribers}
9798
try:
9899
if not self.connect_only:
99100
await self.broker.start()
100101
yield self.broker
101102
finally:
102103
self._fake_close(self.broker)
104+
for sub, was_running in saved_running.items():
105+
sub.running = was_running
103106

104107
@contextmanager
105108
def _patch_producer(self, broker: Broker) -> Iterator[None]:

faststream/asgi/app.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from faststream._internal.logger import logger
1818
from faststream.exceptions import INSTALL_UVICORN, StartupValidationError
1919

20-
from .factories import AsyncAPIRoute
20+
from .factories import AsyncAPIRoute, make_try_it_out_handler
2121
from .handlers import HttpHandler
2222
from .response import AsgiResponse
2323
from .websocket import WebSocketClose
@@ -126,9 +126,23 @@ def __init__(
126126
if asyncapi_path:
127127
asyncapi_route = AsyncAPIRoute.ensure_route(asyncapi_path)
128128
handler = asyncapi_route(self.schema)
129-
handler.set_logger(logger) # type: ignore[attr-defined]
129+
handler.set_logger(logger)
130130
self.routes.append((asyncapi_route.path, handler))
131131

132+
if asyncapi_route.try_it_out and self.broker is not None:
133+
try_it_out_route = make_try_it_out_handler(
134+
self.broker,
135+
include_in_schema=asyncapi_route.include_in_schema,
136+
)
137+
138+
try_it_out_route.update_fd_config(self.config)
139+
try_it_out_route.set_logger(logger)
140+
141+
self.routes.append((
142+
asyncapi_route.try_it_out_url,
143+
try_it_out_route,
144+
))
145+
132146
self._server = OuterRunState()
133147

134148
self._log_level: int = logging.INFO

faststream/asgi/factories.py

Lines changed: 0 additions & 158 deletions
This file was deleted.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from .asyncapi import (
2+
AsyncAPIRoute,
3+
TryItOutProcessor,
4+
make_asyncapi_asgi,
5+
make_try_it_out_handler,
6+
)
7+
from .ping import make_ping_asgi
8+
9+
__all__ = (
10+
"AsyncAPIRoute",
11+
"TryItOutProcessor",
12+
"make_asyncapi_asgi",
13+
"make_ping_asgi",
14+
"make_try_it_out_handler",
15+
)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from .docs import make_asyncapi_asgi
2+
from .route import AsyncAPIRoute
3+
from .try_it_out import TryItOutProcessor, make_try_it_out_handler
4+
5+
__all__ = (
6+
"AsyncAPIRoute",
7+
"TryItOutProcessor",
8+
"make_asyncapi_asgi",
9+
"make_try_it_out_handler",
10+
)

0 commit comments

Comments
 (0)