This release is a pre-release and may not be stable for production use.
sillo-inertia
An Inertia.js adapter for Sillo, with React and Vue.
A handler names a component and returns its props. Whether that becomes a full HTML document or a JSON page object is the adapter's problem, not yours — it depends on whether the client has booted yet, which is a property of the request rather than of your code.
from sillo import silloApp
from sillo.core.http import Request, Response
from sillo_inertia import Inertia, vite_react
app = silloApp()
inertia = Inertia(
app,
root_view="resources/views/app.html",
version="1",
vite=vite_react(dev=True),
)
@app.get("/")
async def home(request: Request, response: Response):
return await inertia.render("Home", {"name": "Sillo"})
Install
pip install sillo-inertia
Or with uv:
uv add sillo-inertia
The root view
Your resources/views/app.html needs the {{ inertia }} placeholder:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
{{ inertia_head }}
</head>
<body>
<div id="{{ root_id }}"></div>
{{ inertia }}
</body>
</html>
{{ inertia }} expands to a <script type="application/json" data-page="app">
element holding the page object — that is where Inertia 2.x and later read it
from. Do not put it on the root <div> as data-page: that was the 1.x
convention, current clients never look there, and the page boots with a null
page object and throws Cannot read properties of null (reading 'component').
If you are targeting a 1.x client, {{ inertia_page }} still gives you the
HTML-escaped attribute value.
Rendering
render
await inertia.render(
"Users/Index", # the component the client resolves
{"users": [...]}, # props
status_code=200,
headers={"X-Total-Count": "42"},
view_data={"title": "Users"}, # placeholders in the root view
)
render returns a response. Return it from the handler; there is no response
object to fill in and nothing to pass along.
The request it answers is the one the middleware is currently handling. If you need a different one — a background job, a test that calls a function directly — pass it by keyword:
await inertia.render("Home", props, request=request)
Without a request from either source, render raises OutsideRequestError
and tells you which of those two cases you are in.
Without the adapter in scope
render, redirect, back and location are importable on their own. They
find the adapter through the same middleware, which means a routes module can
build Inertia responses without importing the module that owns the app — and
so without the circular import that would otherwise cause.
from sillo_inertia import render
@app.get("/")
async def home(request: Request, response: Response):
return await render("Home", {"name": "Sillo"})
As a decorator
When a handler does nothing but produce props, @inertia.page takes the rest:
@app.get("/users/{user_id}")
@inertia.page("Users/Show")
async def show(user_id):
return {"user": await User.get(id=user_id)}
The function declares only what it uses. Ask for request or response by
name and you get them; leave them out and they are not passed. Path parameters
and injected dependencies arrive as usual.
Returning a response instead of a mapping sends that response untouched, so a handler can still redirect out of a page:
@app.post("/users")
@inertia.page("Users/Create")
async def create(request: Request):
form = await request.json()
await User.create(**form)
return inertia.redirect("/users")
Anything you would pass to render can be pinned on the decorator:
@inertia.page("Errors/NotFound", status_code=404)
Props
A prop can be a value, a callable, or a coroutine function. Callables that want the request take one parameter; those that do not, take none.
{
"count": 5, # a value
"total": lambda: Order.count(), # called per request
"mine": lambda request: request.user.orders, # given the request
"stats": fetch_stats, # async, awaited
}
props itself can be a callable returning the whole mapping:
await inertia.render("Home", lambda request: {"path": request.url.path})
Lazy props
A lazy prop is left out of a partial reload unless the client asks for it by name, so the work behind it is skipped on requests that would discard it:
from sillo_inertia import lazy
await inertia.render(
"Posts/Show",
{
"post": post.to_dict(),
"comments": lazy(lambda: Comment.for_post(post.id)),
},
)
On a partial reload — X-Inertia-Partial-Component: Posts/Show and
X-Inertia-Partial-Data: comments — only comments is resolved and returned.
Note that this is narrower than lazy in Inertia's own server adapters, where
a lazy prop is excluded from every regular visit and included only when asked
for by name. Here it is resolved on a full visit like any other prop, and only
partial reloads filter it out. Do not rely on it to keep an expensive query off
the first page load.
Shared props
Props every page gets. Set them once, at startup:
inertia.share(app_name="MyApp", auth={"user": None})
A page's own props win on a name clash.
Redirects
inertia.redirect("/dashboard") # 303 after POST/PUT/PATCH, 302 after GET
inertia.redirect("/home", status_code=301)
inertia.back() # to the Referer
inertia.back(fallback="/posts") # ...when there is no Referer
The 303 is not decoration. On a 302 the browser repeats the POST against the new URL, so a redirect after a successful create creates a second record.
location sends the client out of Inertia entirely — a full browser visit
rather than an XHR one, which is the only way to reach an external URL:
inertia.location("https://billing.example.com/checkout")
Configuration
Inertia(
app=app, # attaches the middleware
root_view="resources/views/app.html",
version="1.0.0", # a string, or a callable returning one
root_id="app", # the mount point's element id
base_dir=".", # where asset paths resolve from
vite=vite_react(dev=True),
)
The adapter can also be attached later:
inertia = Inertia(root_view="resources/views/app.html")
inertia.middleware(app)
Asset versions
When the client's X-Inertia-Version does not match the current one, the
middleware answers with a 409 and X-Inertia-Location before the handler
runs. The client does a full visit and comes back on the current build.
A version of None disables the check.
Passing a callable re-reads it per request, which is what you want when the version comes from a build manifest that changes without a restart.
Vite
vite_react(
entry="src/main.jsx",
dev_server="http://localhost:5173",
manifest_path="dist/.vite/manifest.json",
asset_prefix="/assets/",
dev=True,
react_refresh=True,
)
vite_vue(
entry="src/main.ts",
dev_server="http://localhost:5173",
manifest_path="dist/.vite/manifest.json",
asset_prefix="/assets/",
dev=True,
)
{{ inertia_head }} renders the tags: the dev server's client and entry in
development, the hashed manifest entries and their CSS in production.
Upgrading from 0.0.x
render and redirect no longer take request and response:
# before
return await inertia.render(request, response, "Home", {"name": "Sillo"})
# after
return await inertia.render("Home", {"name": "Sillo"})
The old call raises a TypeError naming the new form, so nothing fails
silently. Inertia.location() is now synchronous — drop the await.
Props callbacks taking a request still work unchanged; the parameter is now
optional rather than required, so lambda _: value can become
lambda: value.
Project Links
- Repository: https://github.com/sillohq/inertia
- Sillo Framework: https://github.com/sillohq/core
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file sillo_inertia-0.0.1a3.tar.gz.
File metadata
- Download URL: sillo_inertia-0.0.1a3.tar.gz
- Upload date:
- Size: 54.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.12.3 {"installer":{"name":"uv","version":"0.12.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2fb5721b62565731bc87d8f73fa0f35e0c7e0174b18071b528d8512f2d24a418
|
|
| MD5 |
a72686bf00c8356c9b573e9108c07347
|
|
| BLAKE2b-256 |
e1f30621b1405a81e7d2bb02a60056d55dd5bd41cfb9c6045f603576fffccacb
|
File details
Details for the file sillo_inertia-0.0.1a3-py3-none-any.whl.
File metadata
- Download URL: sillo_inertia-0.0.1a3-py3-none-any.whl
- Upload date:
- Size: 16.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.12.3 {"installer":{"name":"uv","version":"0.12.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
32727bf31156f69dc09524e48e9643cd83bf0a8e041905a0439d076b019abe0d
|
|
| MD5 |
46f38614992c0277ddd9bf7ed6ccc2dc
|
|
| BLAKE2b-256 |
2966eca29285d9d1af719e20a9e6dd412676c0aabd3da926bc9fc24cb20fad0d
|