ReactPyMiddleware
Overview¶
ReactPyMiddleware adds ReactPy routes to an ASGI application you already have. Requests ReactPy does not recognise fall through to your app.
from reactpy.executors.asgi import ReactPyMiddleware
my_app = ReactPyMiddleware(existing_asgi_app, root_components=["main.component"])
Reference¶
ReactPyMiddleware(app, root_components, **settings)¶
from reactpy.executors.asgi import ReactPyMiddleware
Parameters¶
-
app: The ASGI application to serve when a request does not match a ReactPy route. Any ASGI app works — Starlette, FastAPI, or hand-written. -
root_components: A list, set, or tuple of dotted paths to root components, e.g.["main.counter"]. The path must be importable by Python's import system: module path, dot, attribute name. Resolution ismodule.rsplit(".", 1)followed bygetattr, so it must name a module that is importable from the server's working directory. -
**settings: Any key fromReactPyConfig. See Configuration.path_prefixis validated at construction and raisesValueErrorif malformed.
Returns¶
An ASGI application.
Usage¶
Wrapping an existing app¶
1 | |
1 | |
The wrapped app is untouched. Verified with Starlette: a plain /healthz route still returned its text response through the middleware.
What the middleware actually claims¶
ReactPyMiddleware.__call__ handles exactly four kinds of request and forwards everything else:
| Path | Handled by |
|---|---|
websocket matching {path_prefix}{dotted_path}/ | ReactPy render stream |
{path_prefix}static/... | ReactPy client bundle |
{path_prefix}modules/... | built web modules |
| user-registered routes | your handlers |
| anything else | your app |
The middleware never serves your HTML
This is the part that surprises people. Registering a dotted path does not create a page. Requesting "/" falls through to your app, and there is no {path_prefix}{dotted_path}/ HTTP route either — that pattern is matched only when the ASGI scope type is "websocket". Verified: requesting /reactpy/main.counter/ over HTTP returns 404, while the websocket on the same path streams the model.
You produce the document yourself, by emitting an empty mount element and the client bootstrap that points at one registered component.
Serving a component from your own route¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
1 | |
server_side_component_html emits the <div id="app">, the import map fallback, and the mountReactPy(...) call. Verified in a real browser: the counter rendered into <div id="app"> with [ReactPy] Connected! in the console.
The trailing slash in component_path is required
Pass "main.counter/", not "main.counter". The client opens ws://host/reactpy/<component_path>?path=/, and the server matches the websocket path against
^{path_prefix}(?P<dotted_path>[a-zA-Z0-9_.]+)/$
which requires the trailing slash. Verified both ways: without it the handshake fails with Unexpected response code and the console logs [ReactPy] Failed to connect!, leaving an empty div; with it, the component renders.
Multiple components¶
Register several and choose per page. Each page mounts exactly one:
my_app = ReactPyMiddleware(
starlette_app,
root_components=["main.counter", "main.settings", "main.profile"],
)
Then emit a different component_path from each of your routes.
Unregistered paths are rejected
"Attempting to use an unregistered root component <path>." is raised when a websocket asks for a dotted path you never listed. Register every component you intend to mount.
Import errors¶
A dotted path that cannot be imported fails at construction, with a message naming the part that failed:
ImportError: ReactPy failed to import "main"
AttributeError: ReactPy failed to import "counter" from "main"
The first means the module was not found; the second means the module loaded but has no such attribute. ValueError is raised earlier still if the path has no dot at all.
Choosing an executor¶
| Goal | Use |
|---|---|
| Whole app is ReactPy, no existing server code | ReactPy |
| Add components to an existing ASGI app | ReactPyMiddleware |
| Run component code in the browser via PyScript | ReactPyCsr |
ReactPy is itself a subclass of ReactPyMiddleware that registers a single root and serves its own index document, which is why the standalone executor can do the HTML the middleware leaves to you.
Related¶
- Adding ReactPy to an Existing Project
- ReactPy — the standalone executor.