Skip to main content

FastAPI server-side rendering with built-in HTMX support.

Project description

Tests Linters Documentation PyPI package

Source code: https://github.com/volfpeter/fasthx

Documentation and examples: https://volfpeter.github.io/fasthx

FastHX

FastAPI server-side rendering with built-in HTMX support.

Key features

  • Decorator syntax that works with FastAPI as one would expect, no need for unused or magic dependencies in routes.
  • Built for HTMX, but can be used without it.
  • Works with any templating engine or server-side rendering library, e.g. htmy, jinja2, or dominate.
  • Gives the rendering engine access to all dependencies of the decorated route.
  • HTMX routes work as expected if they receive non-HTMX requests, so the same route can serve data and render HTML at the same time.
  • Response headers you set in your routes are kept after rendering, as you would expect in FastAPI.
  • Correct typing makes it possible to apply other (typed) decorators to your routes.
  • Works with both sync and async routes.

Support

Consider supporting the development and maintenance of the project through sponsoring, or reach out for consulting so you can get the most out of the library.

Installation

The package is available on PyPI and can be installed with:

$ pip install fasthx

The package has optional dependencies for the following official integrations:

  • htmy: pip install fasthx[htmy].
  • jinja: pip install fasthx[jinja].

Core concepts

The core concept of FastHX is to let FastAPI routes do their usual job of handling the business logic and returning the result, while the FastHX decorators take care of the entire rendering / presentation layer using a declarative, decorator-based approach.

Interally, FastHX decorators always have access to the decorated route's result, all of its arguments (sometimes called the request context), and the current request. Integrations convert these values into data that can be consumed by the used rendering engine (for example htmy or jinja), run the rendering engine with the selected component (more on this below) and the created data, and return the result to the client. For more details on how data conversion works and how it can be customized, please see the API documentation of the rendering engine integration of your choice.

The ComponentSelector abstraction makes it possible to declaratively specify and dynamically select the component that should be used to render the response to a given request. It is also possible to define an "error" ComponentSelector that is used if the decorated route raises an exception -- a typical use-case being error rendering for incorrect user input.

Examples

For complete, but simple examples that showcase the basic use of FastHX, please see the examples folder.

HTMY templating

Requires: pip install fasthx[htmy].

Serving HTML and HTMX requests with htmy is as easy as creating a fasthx.htmy.HTMY instance and using its hx() and page() decorator methods on your routes.

The example below assumes the existence of an IndexPage and a UserList htmy component. The full working example with the htmy components can be found here.

from datetime import date

from fastapi import FastAPI
from pydantic import BaseModel

from fasthx.htmy import HTMY

# Pydantic model for the application
class User(BaseModel):
    name: str
    birthday: date

# Create the FastAPI application.
app = FastAPI()

# Create the FastHX HTMY instance that renders all route results.
htmy = HTMY()

@app.get("/users")
@htmy.hx(UserList)  # Render the result using the UserList component.
def get_users(rerenders: int = 0) -> list[User]:
    return [
        User(name="John", birthday=date(1940, 10, 9)),
        User(name="Paul", birthday=date(1942, 6, 18)),
        User(name="George", birthday=date(1943, 2, 25)),
        User(name="Ringo", birthday=date(1940, 7, 7)),
    ]

@app.get("/")
@htmy.page(IndexPage)  # Render the index page.
def index() -> None: ...

Jinja2 templating

Requires: pip install fasthx[jinja].

To start serving HTML and HTMX requests, all you need to do is create an instance of fasthx.Jinja and use its hx() or page() methods as decorators on your routes. hx() only triggers HTML rendering for HTMX requests, while page() unconditionally renders HTML. See the example code below:

from fastapi import FastAPI
from fastapi.templating import Jinja2Templates
from fasthx import Jinja
from pydantic import BaseModel

# Pydantic model of the data the example API is using.
class User(BaseModel):
    first_name: str
    last_name: str

# Create the app.
app = FastAPI()

# Create a FastAPI Jinja2Templates instance and use it to create a
# FastHX Jinja instance that will serve as your decorator.
jinja = Jinja(Jinja2Templates("templates"))

@app.get("/")
@jinja.page("index.html")
def index() -> None:
    ...

@app.get("/user-list")
@jinja.hx("user-list.html")
async def htmx_or_data() -> list[User]:
    return [
        User(first_name="John", last_name="Lennon"),
        User(first_name="Paul", last_name="McCartney"),
        User(first_name="George", last_name="Harrison"),
        User(first_name="Ringo", last_name="Starr"),
    ]

@app.get("/admin-list")
@jinja.hx("user-list.html", no_data=True)
def htmx_only() -> list[User]:
    return [User(first_name="Billy", last_name="Shears")]

See the full working example here.

Custom templating

Requires: pip install fasthx.

If you would like to use a rendering engine without FastHX integration, you can easily build on the hx() and page() decorators which give you all the functionality you will need. All you need to do is implement the HTMLRenderer protocol.

Similarly to the Jinja case, hx() only triggers HTML rendering for HTMX requests, while page() unconditionally renders HTML. See the example code below:

from typing import Annotated, Any

from fastapi import Depends, FastAPI, Request
from fasthx import hx, page

# Create the app.
app = FastAPI()

# Create a dependecy to see that its return value is available in the render function.
def get_random_number() -> int:
    return 4  # Chosen by fair dice roll.

DependsRandomNumber = Annotated[int, Depends(get_random_number)]

# Create the render methods: they must always have these three arguments.
# If you're using static type checkers, the type hint of `result` must match
# the return type annotation of the route on which this render method is used.
def render_index(result: list[dict[str, str]], *, context: dict[str, Any], request: Request) -> str:
    return "<h1>Hello FastHX</h1>"

def render_user_list(result: list[dict[str, str]], *, context: dict[str, Any], request: Request) -> str:
    # The value of the `DependsRandomNumber` dependency is accessible with the same name as in the route.
    random_number = context["random_number"]
    lucky_number = f"<h1>{random_number}</h1>"
    users = "".join(("<ul>", *(f"<li>{u['name']}</li>" for u in result), "</ul>"))
    return f"{lucky_number}\n{users}"

@app.get("/")
@page(render_index)
def index() -> None:
    ...

@app.get("/htmx-or-data")
@hx(render_user_list)
def htmx_or_data(random_number: DependsRandomNumber) -> list[dict[str, str]]:
    return [{"name": "Joe"}]

@app.get("/htmx-only")
@hx(render_user_list, no_data=True)
async def htmx_only(random_number: DependsRandomNumber) -> list[dict[str, str]]:
    return [{"name": "Joe"}]

See the full working example here.

External examples

  • FastAPI-HTMX-Tailwind example: A complex Jinja2 example with features like active search, lazy-loading, server-sent events, custom server-side HTMX triggers, dialogs, and TailwindCSS and DaisyUI integration.

Dependencies

The only dependency of this package is fastapi.

Development

Use ruff for linting and formatting, mypy for static code analysis, and pytest for testing.

The documentation is built with mkdocs-material and mkdocstrings.

Contributing

We welcome contributions from the community to help improve the project! Whether you're an experienced developer or just starting out, there are many ways you can contribute:

  • Discuss: Join our Discussion Board to ask questions, share ideas, provide feedback, and engage with the community.
  • Document: Help improve the documentation by fixing typos, adding examples, and updating guides to make it easier for others to use the project.
  • Develop: Prototype requested features or pick up issues from the issue tracker.
  • Share: Share your own project by adding it to the external examples list, helping others discover and benefit from your work.
  • Test: Write tests to improve coverage and enhance reliability.

License - MIT

The package is open-sourced under the conditions of the MIT license.

Thank you

Thank you to Smart-Now for supporting the project.

Project details


Download files

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

Source Distribution

fasthx-2.3.0.tar.gz (18.0 kB view details)

Uploaded Source

Built Distribution

fasthx-2.3.0-py3-none-any.whl (18.7 kB view details)

Uploaded Python 3

File details

Details for the file fasthx-2.3.0.tar.gz.

File metadata

  • Download URL: fasthx-2.3.0.tar.gz
  • Upload date:
  • Size: 18.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.12.9

File hashes

Hashes for fasthx-2.3.0.tar.gz
Algorithm Hash digest
SHA256 8af01104cc8a62fc1e7a16928948050ce6b623c966735be415efa8e97a958aca
MD5 1b12c4cf7056ac21ca4415631344bef9
BLAKE2b-256 4b2cb4dbfb60f3167d885664cced6564c7f2be8c51c82cc069603367208d108b

See more details on using hashes here.

Provenance

The following attestation bundles were made for fasthx-2.3.0.tar.gz:

Publisher: publish.yml on volfpeter/fasthx

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file fasthx-2.3.0-py3-none-any.whl.

File metadata

  • Download URL: fasthx-2.3.0-py3-none-any.whl
  • Upload date:
  • Size: 18.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.12.9

File hashes

Hashes for fasthx-2.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 c72b23f16690b5d40f5e49aedbe8861ca592f87494d735a2b62ef344a929dafa
MD5 16092120cc79731ed12c318bf592276f
BLAKE2b-256 71119e8e1826141c899b5b1d461da20b63e7019773a03a2f5ff45fe55eb87f7b

See more details on using hashes here.

Provenance

The following attestation bundles were made for fasthx-2.3.0-py3-none-any.whl:

Publisher: publish.yml on volfpeter/fasthx

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page