He estado jugando con FastAPI para entender como de factible es crear soluciones sitios con un enfoque de HTML sobre WebSockets (LiveView). Me ha sorprendido en algunos aspectos, tanto para bien como para mal.
Empecemos con la demostración.
Se ha creado 2 páginas, una de inicio y otra de sobre nosotros. Se puede navegar entre ellas sin recargar la página. Además se ha creado un botón que cuando es pulsado muestra el tiempo actual en un elemento del DOM.
Código
Para el frontend, he utilizado un pack todo en uno: Django LiveView FrontEnd. Este paquete incluye la gestión de conexión, eventos y respuestas.
Puedes encontrar todo el código en el siguiente repositorio.
El código de FastAPI es el siguiente:
import os
import pathlib
from fastapi import FastAPI, Request, WebSocket
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
import home
import about_us
app = FastAPI()
app.mount('/static', StaticFiles(directory='static'), name='static')
BASE_DIR = pathlib.Path(__file__).parent
templates = Jinja2Templates(
directory=[
BASE_DIR / 'templates',
]
)
@app.get('/')
async def welcome_page(request: Request):
context = {
'request': request,
}
return templates.TemplateResponse(
'layouts/base.html', context,
)
@app.websocket('/ws/liveview/')
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
while True:
data_frontend = await websocket.receive_json()
if data_frontend and 'action' in data_frontend:
action_data = data_frontend['action'].split('->')
if len(action_data) == 2:
action = action_data[0].lower()
function = action_data[1].lower()
await eval(
f'{action}.{function}(websocket, templates, data_frontend)'
)
En webcome_page
se renderiza la página de inicio y en websocket_endpoint
se manejan los eventos de LiveView.
La página base.html
es sencilla.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Example</title>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, shrink-to-fit=no"
>
<!-- Django LiveView FrontEnd -->
<!-- https://django-liveview.andros.dev/docs/ -->
<script defer type="module" src="https://cdn.jsdelivr.net/gh/Django-LiveView/frontend/js/main.js"></script>
</head>
<body
data-controller="page"
>
<header>
<h1>FastAPI with LiveView</h1>
<nav>
<ul>
<li>
<a
href="#"
data-action="click->page#changePage"
data-page="home"
role="button"
>Home</a>
</li>
<li>
<a
href="#"
data-action="click->page#changePage"
data-page="about_us"
role="button"
>About Us</a>
</ul>
</nav>
</header>
<main id="main"></main>
<footer></footer>
</body>
</html>
Puedes ver como he añadido 2 enlaces en el menú de navegación que cambian la página sin recargarla.
La página de home.html
es tan solo texto estático.
<section>
<h1>Home</h1>
<p>
In dictum non, consectetur a erat nam at lectus urna duis convallis convallis? Duis tristique sollicitudin nibh sit amet commodo nulla facilisi nullam vehicula ipsum a arcu cursus vitae congue mauris rhoncus aenean vel elit? Aliquet sagittis id consectetur purus. Pellentesque adipiscing commodo elit, at imperdiet dui accumsan sit. Praesent elementum facilisis leo, vel fringilla est ullamcorper eget nulla facilisi etiam dignissim diam quis enim lobortis scelerisque fermentum dui faucibus in ornare quam viverra orci sagittis eu volutpat! Dui, id ornare arcu odio ut sem nulla pharetra diam sit amet nisl suscipit adipiscing bibendum est ultricies integer quis! Et leo duis ut diam quam nulla porttitor massa id neque! Morbi non arcu risus, quis varius quam quisque id diam vel quam elementum pulvinar etiam! Id faucibus nisl tincidunt eget nullam non nisi est, sit amet facilisis magna etiam tempor, orci eu lobortis elementum, nibh tellus molestie nunc, non blandit massa enim nec dui nunc! Cras ornare arcu dui vivamus arcu felis, bibendum ut tristique et, egestas quis ipsum suspendisse ultrices gravida dictum fusce ut placerat orci nulla pellentesque dignissim! Commodo viverra maecenas accumsan, lacus vel facilisis volutpat, est velit egestas dui, id ornare arcu odio ut sem nulla pharetra diam.
</p>
</section>
Aunque en about_us.html
he añadido un botón que muestra la hora actual.
<section>
<h1>About us</h1>
<p>
<button
data-action="click->page#run"
data-liveview-action="about_us"
data-liveview-function="current_time"
>Get time</button>
</p>
<p id="time"></p>
</section>
Las funcionalidades de home
se encuentra en home.py
.
async def send_page(websocket, templates, data_frontend):
await websocket.send_json(
{
'selector': '#main',
'html': templates.get_template(
f'pages/home.html'
).render(),
}
)
Y las de about_us
en about_us.py
.
from datetime import datetime
async def send_page(websocket, templates, data_frontend):
await websocket.send_json(
{
'selector': '#main',
'html': templates.get_template(
f'pages/about_us.html'
).render(),
}
)
async def current_time(websocket, templates, data_frontend):
await websocket.send_json(
{
'selector': '#time',
'html': f"<datetime>{datetime.now()}</datetime>",
}
)
Cuando se pulsa el botón, se envía la etiqueta datetime
con el output de datetime.now()
.
Y eso sería todo. En el respositorio podéis levantarlo para problarlo por vosotros mismos.
Puntos positivos
- Facilidad a la hora de levantar un servidor de WebSockets: FastAPI es muy sencillo de usar y de configurar, tiene su propio paquete para manejar WebSockets.
- Rápido de configurar y lanzar: En cuestión de minutos puedes tener una web levantada. Recuerda a Flask y similares.
Puntos negativos
- No esta orientado a crear sitios HTML: Dependes de paquetes externos, como
starlette
para manejar las rutas yjinja2
para renderizar las plantillas. Además, si incluyes formularios, tendrás que hacerlo a mano o usar otros paquetes comowtforms
. - No es posible el envío masivo o grupos de clientes: El servidor solo habla con el cliente conectado. No es posible enviar mensajes a todos los clientes o una selección concreta. Y si es posible hacerlo, no esta documentado.
- No gestiona scopes, sesiones o cookies mediante WebSockets: No es posible guardar información del cliente en el servidor, como una sesión o un token. Tendrás que hacerlo a mano o usar otros paquetes como
fastapi-sessions
.
Conclusión
Tal vez el problema era que estaba metiendo con calzador un desarrollo web en un software que fue diseñado para realizar únicamente APIs. Después de la experiencia sigo opinando que Django, junto a Channels, es la opción más completa para realizar desarrollos web con WebSockets en el ecosistema de Python. El tiempo que ahorras en el inicio tiene un coste en la complejidad del desarrollo en el futuro.
{{ comments.length }} comentarios