Skip to main content
Pre-release

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

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

sillo_inertia-0.0.1a3.tar.gz (54.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

sillo_inertia-0.0.1a3-py3-none-any.whl (16.0 kB view details)

Uploaded Python 3

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

Hashes for sillo_inertia-0.0.1a3.tar.gz
Algorithm Hash digest
SHA256 2fb5721b62565731bc87d8f73fa0f35e0c7e0174b18071b528d8512f2d24a418
MD5 a72686bf00c8356c9b573e9108c07347
BLAKE2b-256 e1f30621b1405a81e7d2bb02a60056d55dd5bd41cfb9c6045f603576fffccacb

See more details on using hashes here.

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

Hashes for sillo_inertia-0.0.1a3-py3-none-any.whl
Algorithm Hash digest
SHA256 32727bf31156f69dc09524e48e9643cd83bf0a8e041905a0439d076b019abe0d
MD5 46f38614992c0277ddd9bf7ed6ccc2dc
BLAKE2b-256 2966eca29285d9d1af719e20a9e6dd412676c0aabd3da926bc9fc24cb20fad0d

See more details on using hashes here.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page