Configuración de la Aplicación
Propósito: Configurar e iniciar la aplicación. main.py es el punto de entrada principal donde se configura e inicializa la aplicación.
from lila.core.app import App
from app.routes.routes import routes
from app.routes.api import routes as api_routes
from lila.core.middleware import Middleware
from app.config import PORT, HOST, DEBUG
from app.middlewares.defaults import (
LoggingMiddleware,
IPBlockingMiddleware,
URLBlockingMiddleware,
MaliciousExtensionMiddleware,
SensitivePathMiddleware,
ErrorHandlerMiddleware,
)
import itertools
import uvicorn
import asyncio
# English: Combining application and API routes into a single list.
# Español: Combinando las rutas de la aplicación y la API en una única lista.
all_routes = list(itertools.chain(routes, api_routes))
# English: Here we activate the admin panel with default settings.
# Español: Aquí activamos el panel de administrador con configuraciones predeterminadas.
# from app.routes.admin import Admin
# from app.models.user import User
# admin_routes=Admin(models=[User])
# all_routes = list(itertools.chain(routes, api_routes,admin_routes))
# English: Marker for the admin routes in main.py.
# Español: Marcador para las rutas de administrador en main.py.
# admin_marker
cors = None
# English: CORS usage example
# Español : Ejemplo de utilización de CORS
# cors={
# "origin": ["*"],
# "allow_credentials" : True,
# "allow_methods":["*"],
# "allow_headers": ["*"]
# }
# app = App(debug=True, routes=all_routes,cors=cors)
# English:necessary for cli command modify react cors for development
# Español:necesario para el comando cli modificar cors de react para desarrollo
# react_marker
middlewares = [
Middleware(LoggingMiddleware),
Middleware(IPBlockingMiddleware),
Middleware(URLBlockingMiddleware),
Middleware(MaliciousExtensionMiddleware),
Middleware(SensitivePathMiddleware),
Middleware(ErrorHandlerMiddleware),
]
# English: Initializing the application with debugging enabled and the combined routes.
# Español: Inicializando la aplicación con la depuración activada y las rutas combinadas.
app = App(debug=DEBUG, routes=all_routes, cors=cors, middleware=middlewares)
# English: To ensure SEO (bots, AI), caching, and HTML hydration, uncomment these lines.
# Adding {% include "react/cache/index.html" %} to
# Español :Para tener seo (bots,ia) ,cache y que react hidrate el html ,descomenta estas lineas.
# Agregadando en , {% include "react/cache/index.html" %}
# import subprocess
# import sys
# @app.on_event("startup")
# async def startup_event():
# print("♻️ Prerender for react...")
# url_with_port =f" http://{HOST}:{PORT}"
# subprocess.Popen([
# sys.executable,
# "-m",
# "cli.prerender",
# "--url",
# url_with_port.strip()
# ])
# English: Asynchronous main function to run the application server.
# Español: Función principal asíncrona para ejecutar el servidor de la aplicación.
async def main():
# English: Starting the Uvicorn server with the application instance.
# Español: Iniciando el servidor Uvicorn con la instancia de la aplicación.
uvicorn.run("main:app", host=HOST, port=PORT, reload=True)
# English: Entry point for the script, running the main asynchronous function.
# Español: Punto de entrada del script, ejecutando la función principal asíncrona.
if __name__ == "__main__":
try:
asyncio.run(main())
except KeyboardInterrupt:
# English: Gracefully shutting down the application on keyboard interrupt.
# Español: Apagando la aplicación de manera ordenada al interrumpir con el teclado.
print("Shutting down the application...")
pass
Rutas
Las rutas son los puntos de acceso a la aplicación. En Lila, las rutas se definen en el
directorio
routes (por defecto, aunque puedes ubicarlas donde
desees) y
se importan en main.py para su uso. Las rutas pueden
configurarse
para manejar solicitudes HTTP, métodos de API y más.
Además de JSONResponse, puedes usar HTMLResponse,
RedirectResponse, PlainTextResponse o
StreamingResponse para transmitir datos en tiempo real
(útil para video/audio en streaming o respuestas grandes).
A continuación, un ejemplo de cómo se definen las rutas en Lila:
routes/api.py
from lila.core.responses import JSONResponse
from lila.core.routing import Router
from lila.core.request import Request
router = Router()
@router.route(path='/api', methods=['GET'])
async def api(request: Request):
"""Función API"""
return JSONResponse({'api': True})
Obtener Parámetros de URL
En esta función recibimos un parámetro a través de la URL usando {param}.
Si no se proporciona, se usa 'default'.
La respuesta es un JSON con el valor recibido.
Recibiendo Parámetros GET
@router.route(path='/url_with_param/{param}', methods=['GET'])
async def param(request: Request):
param = request.path_params.get('param', 'default')
return JSONResponse({"received_param": param})
También se puede hacer así:
Recibiendo Parámetros de Consulta GET
@router.route(path='/url_with_param/?query_param', methods=['GET','POST'])
async def query_param(request: Request):
query_param = request.query_params.get('query_param', 'query_param')
return JSONResponse({"received_param": query_param})
Uso de Middleware y Decoradores
Los middlewares permiten interceptar las solicitudes antes de que lleguen a la lógica
principal de la API.
En este ejemplo usamos @validate_token para validar un
token JWT en el encabezado.
Validación de Middleware
@router.route(path='/api/token', methods=['GET','POST'])
@validate_token # Middleware para validar token JWT.
async def api_token(request: Request):
return JSONResponse({'api': True})
Validación de Datos con Pydantic
Pydantic permite definir modelos de datos que validan automáticamente la entrada del
usuario.
Además, especificar un modelo en la ruta genera documentación automática en /docs.
Validación con Pydantic
from pydantic import EmailStr, BaseModel
class ExampleModel(BaseModel):
email: EmailStr # Valida que sea un email válido.
password: str # Cadena de texto para contraseña.
@router.route(path='/api/example', methods=['POST'], model=ExampleModel)
async def login(request: Request):
body = await request.json()
try:
input = ExampleModel(**body) # Validación automática con Pydantic.
except Exception as e:
return JSONResponse({"success": False, "msg": f"JSON inválido: {e}"}, status_code=400)
return JSONResponse({"email": input.email, "password": input.password})
Generación Automática de Documentación
Gracias a la integración con Pydantic, la documentación de la API se genera
automáticamente
y es accesible desde /docs. También puedes generar un
archivo
OpenAPI JSON para herramientas externas.
Generación de Documentación
router.swagger_ui() # Activa Swagger UI para la documentación de la API.
router.openapi_json() # Genera OpenAPI JSON para herramientas externas.
Importando Rutas en main.py
Para usar las rutas definidas en el router, debes obtenerlas con
router.get_routes() e importarlas en main.py.
Importación de Rutas
routes = router.get_routes() # Obtiene todas las rutas definidas.
Prefijos de Rutas y Atajos para
Métodos HTTP
Las rutas ahora pueden tener un prefix para agrupar
múltiples endpoints bajo un mismo camino.
Además, puedes definir rutas directamente con métodos HTTP: get,
post, put y delete.
routes/v1_api_example.py
from lila.core.responses import JSONResponse
from lila.core.routing import Router
from lila.core.request import Request
from pydantic import BaseModel
router = Router(prefix="v1/api")
# ---------------- Ejemplo GET ----------------
@router.get("/items/{item_id}")
async def get_item(request: Request):
"""Obtener un ítem por ID."""
item_id = request.path_params.get("item_id")
return JSONResponse({"item_id": item_id, "status": "fetched"})
# ---------------- Ejemplo POST ----------------
class CreateItemModel(BaseModel):
name: str
description: str
@router.post("/items", model=CreateItemModel)
async def create_item(request: Request):
"""Crear un nuevo ítem."""
body = await request.json()
input_data = CreateItemModel(**body)
return JSONResponse({"name": input_data.name, "description": input_data.description})
# ---------------- Ejemplo PUT ----------------
class UpdateItemModel(BaseModel):
name: str
description: str
@router.put("/items/{item_id}", model=UpdateItemModel)
async def update_item(request: Request):
"""Actualizar un ítem existente por ID."""
item_id = request.path_params.get("item_id")
body = await request.json()
input_data = UpdateItemModel(**body)
return JSONResponse({"item_id": item_id, "updated_name": input_data.name})
# ---------------- Ejemplo DELETE ----------------
@router.delete("/items/{item_id}")
async def delete_item(request: Request):
"""Eliminar un ítem por ID."""
item_id = request.path_params.get("item_id")
return JSONResponse({"item_id": item_id, "status": "deleted"})
Estos ejemplos muestran cómo:
- Usar un
prefix para versionar endpoints.
- Usar decoradores específicos por método como
@router.get, @router.post, etc.
- Validar automáticamente la entrada y generar documentación usando modelos Pydantic.
- Recibir parámetros por URL y cuerpos JSON.
La documentación se genera automáticamente en /docs y /openapi.json.
Archivos Estáticos
Para cargar archivos estáticos (JS, CSS, etc.), puedes usar el método mount().
Los parámetros por defecto son:
path: str = '/public', directory: str = 'static', name: str = 'static'
Estático
from lila.core.routing import Router
# Creando una instancia del Enrutador
router = Router()
# Montando los archivos estáticos en la carpeta 'static', con URL '/public' por defecto
router.mount()
Renderizado de Plantillas (Jinja2 )
En Lila, puedes usar
Jinja2 para
renderizar
HTML con datos del servidor
Renderizado con
Jinja2
Jinja2 es el motor de plantillas por defecto para renderizar HTML. Puedes pasar
datos como
traducciones, valores, listas o diccionarios usando el parámetro context.
Uso Básico en Rutas
@router.route(path="/", methods=["GET"])
async def home(request: Request):
context = {}
response = render(
request=request,
template="index",
context=context
)
# Renderiza la plantilla 'index'
return response
La Función
render
La función
render
se encuentra en
lila.core.templates:
def render(
request: Request,
template: str,
context: dict = {},
theme_: bool = True,
translate: bool = True,
files_translate: list = [],
lang_default: str = None,
templates=templates
)
Descripción de
Parámetros
-
request:
The
Request
object.
-
template
(str):
Template name (without the
.html
extension).
-
context
(dict):
Data to pass to the template (dictionaries, lists, etc.).
-
theme_
(bool, default=True):
Applies the default
visual theme.
-
translate
(bool, default=True):
Enables the built-in
translation system.
-
files_translate
(list):
List of additional
translation files.
-
lang_default
(str):
Forced default language (ideal for SEO in routes like
/es,
/en,
etc.).
-
templates:
Base template
collection used by Lila.
In Lila, you can also use
translate
to pass translations to Jinja2, and parameters like
title
or
version
(ideal for structuring APIs: "v1", "v2").
Optimización de Recursos en Producción (Imágenes y CSS)
Imágenes Optimizadas con Jinja2
Lila optimiza automáticamente todas las imágenes para mejorar el rendimiento
y el SEO. Al usar la función image() en tus templates, el sistema
convierte las imágenes a formato WebP, las redimensiona si
es necesario y las comprime sin perder calidad visual.
Uso en HTML (Jinja2)
HTML
<img src="{{ image('img/lila.png') }}" alt="Lila" width="100" height="100" />
Qué sucede automáticamente
- La imagen se convierte a WebP.
- Si supera los 1920px de ancho, se redimensiona.
- Se comprime con calidad optimizada.
- Se guarda en caché para futuros accesos.
Minificación Automática de CSS
La función public() detecta automáticamente si existe una versión
minificada de tu archivo CSS. Si no existe, la genera automáticamente para
mejorar la velocidad de carga y el SEO.
Uso en HTML (Jinja2)
HTML
<link rel="stylesheet" href="{{ public('css/lila.css') }}" />
Comportamiento Automático
- Si existe
lila.min.css, se usa automáticamente.
- Si no existe, se genera en tiempo real.
- Los archivos minificados cargan más rápido.
Inyección de Contexto Base
Todo el sistema de optimización funciona gracias al contexto base que se
inyecta automáticamente a los templates Jinja2:
Python
def get_base_context(request, files_translate=[], lang_default=None):
context = {
"title": TITLE_PROJECT,
"version": VERSION_PROJECT,
"lang": lang_default if lang_default else lang(request),
"translate": t("translations", request, lang_default=lang_default),
"image": image,
"static": public,
"public": public
}
for file_name in files_translate:
context["translate"].update(
t(file_name, request, lang_default=lang_default)
)
return context
Beneficios de Rendimiento y SEO
- ✅ Carga de páginas más rápida
- ✅ Reducción de peso de archivos
- ✅ Mejor puntaje en Google Lighthouse
- ✅ Imágenes automáticas en WebP
- ✅ Optimización SEO transparente
- ✅ Sin configuración manual
Gestión de
Sesiones
Lila proporciona un sistema de gestión de sesiones simple pero potente construido sobre
itsdangerous
para datos de sesión firmados criptográficamente. Esto asegura que los datos de sesión
no puedan ser manipulados en el lado del cliente.
La clase
Session
proporciona un conjunto de métodos estáticos para manejar los datos de la sesión.
Establecer Datos de
Sesión
El método
setSession
se utiliza para almacenar datos de sesión en una cookie. Puede manejar cadenas,
diccionarios y listas, que se serializan automáticamente a JSON.
ejemplo de
setSession
from lila.core.session import Session
from lila.core.responses import JSONResponse
@router.route("/set-session", methods=["GET"])
async def set_session_example(request: Request):
response = JSONResponse({"message": "La sesión ha sido establecida"})
session_data = {"user_id": 123, "username": "lila_user"}
Session.setSession(new_val=session_data, response=response, name_cookie="user_session")
return response
El método
setSession
acepta varios parámetros para personalizar la cookie, como
secure,
samesite,
max_age,
etc.
Obtener Datos de Sesión
El método
getSession
recupera los datos de sesión brutos y firmados de una cookie. Para obtener los datos sin
firmar y validados, use
getSessionValue
o
unsign.
Desfirmar y Validar
Datos de Sesión
Los métodos
unsign
y
getSessionValue
obtienen los datos de la sesión en su forma original después de verificar la firma.
getSessionValue
se recomienda porque también maneja con gracia las firmas caducadas.
ejemplo de
getSessionValue
from lila.core.session import Session
@router.route("/get-session", methods=["GET"])
async def get_session_example(request: Request):
session_data = Session.getSessionValue(request=request, key="user_session")
if session_data:
return JSONResponse({"session_data": session_data})
else:
return JSONResponse({"message": "No se encontró sesión o la sesión es inválida/caducada."}, status_code=401)
Eliminar una Sesión
El método
deleteSession
se utiliza para eliminar una cookie de sesión del navegador del cliente.
ejemplo de
deleteSession
from lila.core.session import Session
from lila.core.responses import JSONResponse
@router.route("/logout", methods=["GET"])
async def logout(request: Request):
response = JSONResponse({"message": "Sesión cerrada con éxito"})
Session.deleteSession(response=response, name_cookie="user_session")
return response
Referencia Completa de
la Clase Session
Aquí está el código completo de la clase
Session
para su referencia.
core/session.py
import json
from app.config import SECRET_KEY
from itsdangerous import BadSignature, URLSafeTimedSerializer, SignatureExpired
from lila.core.request import Request
from typing import Any, Union, Optional, Dict, List
from lila.core.logger import Logger
serializer = URLSafeTimedSerializer(SECRET_KEY)
class Session:
@staticmethod
def setSession(
new_val: str | dict | list,
response,
name_cookie: str = "session",
secure: bool = True,
samesite: str = "strict",
max_age: int = 3600,
domain: Optional[str] = None,
http_only: bool = True,
path: str = "/",
) -> bool:
try:
if isinstance(new_val, (dict, list)):
new_val = json.dumps(new_val)
else:
new_val = str(new_val)
signed_session = serializer.dumps(new_val)
response.set_cookie(
key=name_cookie,
value=signed_session,
max_age=max_age,
expires=max_age,
secure=secure,
httponly=http_only,
samesite=samesite,
domain=domain,
path=path,
)
return True
except (TypeError, ValueError, Exception) as e:
Logger.error(f"Error setting session: {str(e)}")
print(f"Error session {str(e)}")
return False
@staticmethod
def getSession(key: str, request: Request) -> Optional[str]:
return request.cookies.get(key)
@staticmethod
def unsign(
key: str, request: Request, max_age: Optional[int] = None
) -> Union[Dict, List, str, None]:
session_data = request.cookies.get(key)
if not session_data:
return None
try:
unsigned_data = serializer.loads(session_data, max_age=max_age)
try:
return json.loads(unsigned_data)
except json.JSONDecodeError:
return unsigned_data
except BadSignature:
msg = f"Invalid session signature for cookie: {key}"
print(msg)
Logger.warning(msg)
return None
except Exception as e:
msg = f"Error unsigning session: {str(e)}"
print(msg)
Logger.error(msg)
return None
@staticmethod
def deleteSession(response, name_cookie: str) -> bool:
try:
response.delete_cookie(name_cookie)
return True
except Exception as e:
msg = f"Error deleting session: {str(e)}"
print(e)
Logger.error(msg)
return False
@staticmethod
def getSessionValue(
request: Request, key: str = 'auth', max_age: Optional[int] = 3600
) -> Union[Dict, List, str, None]:
session_data = request.cookies.get(key)
if not session_data:
return None
try:
unsigned_data = serializer.loads(session_data, max_age=max_age)
try:
return json.loads(unsigned_data)
except json.JSONDecodeError:
return unsigned_data
except BadSignature:
msg = f"Invalid session signature for cookie: {key}"
print(msg)
Logger.warning(msg)
return None
except SignatureExpired:
msg = f"Session expired for cookie: {key}"
print(msg)
Logger.warning(msg)
return None
except Exception as e:
msg = f"Error unsigning session: {str(e)}"
print(msg)
Logger.error(msg)
return None
Asistente de Carga de Archivos
El asistente upload
proporciona una solución completa para manejar la carga de archivos en tu
aplicación, incluyendo validación, comprobaciones de seguridad y soporte para
traducciones.
Este asistente maneja automáticamente:
- Validación del método HTTP (solo POST)
- Validación de Content-Type (multipart/form-data)
- Validación de la extensión del archivo
- Límites de tamaño de archivo
- Detección de archivos vacíos
- Manejo seguro de nombres de archivo
- Creación automática de directorios
- Soporte de traducción para mensajes de error
Parámetros
La función upload
acepta estos parámetros:
Parámetros
request: Request # Objeto de solicitud de Starlette
name_file: str | list # Nombre del campo para la carga de archivos (defecto: 'file')
UPLOAD_DIR: str # Directorio para guardar archivos (defecto: 'uploads')
ALLOWED_EXTENSIONS: set # Extensiones de archivo permitidas (defecto: {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'})
MAX_FILE_SIZE: int # Tamaño máximo de archivo en bytes (defecto: 10MB)
Ejemplo de Uso
Implementación de Ruta
from app.helpers.helpers import upload
@router.route("/upload", methods=["POST"])
async def uploadFile(request: Request):
response = await upload(request=request, name_file="file")
return response
Implementación en el Frontend
HTML + JavaScript
<form onsubmit="upload(event);">
<fieldset>
<input type="file" name="file" required>
</fieldset>
<button type="submit">
<i class="icon-check-circle"></i>
Subir Archivos
</button>
</form>
<script>
async function upload(event) {
event.preventDefault();
const formElement = event.target;
const formData = new FormData(formElement);
try {
const response = await fetch('/upload', {
method: 'POST',
body: formData,
});
const result = await response.json();
if (!response.ok) throw new Error(result.message);
alert('¡Archivo subido con éxito!');
} catch (error) {
alert(error.message);
}
}
</script>
Formato de Respuesta
El asistente devuelve una JSONResponse con esta estructura:
Respuesta Exitosa
{
"file": "/uploads/filename.ext",
"success": true,
"message": "Archivo subido con éxito"
}
Respuesta de Error
{
"error": true,
"success": false,
"message": "Mensaje de error en el idioma del usuario"
}
Notas Importantes
Si utilizas windows o el archivo no se crea en el Path recuerda configurar en app/config.py
app/config.py
PATH_UPLOADS = path.join(os.getcwd(), "public", "img", "uploads")
Markdown /HTML
Para renderizar archivos markdown,
se utiliza la función
renderMarkdown,
que recibe los siguientes parámetros:
request, file: str, base_path: str='templates/markdown/', css_files: list=[], js_files: list=[], picocss: bool=False
file
es el nombre del archivo Markdown, comenzando desde base_path.
Por ejemplo: index.md.
Se buscará dentro de templates/markdown/.
css_files
y
js_files
son listas de archivos CSS y JS que se cargarán en el HTML generado.
picocss
es un booleano que indica si se debe cargar el archivo de estilos PicoCSS.
Ahora se puede usar layout.html
en templates/markdown/ como plantilla base editable:
Ejemplo de un archivo Markdown con soporte bilingüe usando translate:
example.md
# {{ translate["welcome"] }}
Este es un ejemplo simple de **Markdown**.
## {{ translate["features"] }}
- {{ translate["easy_write"] }}
- {{ translate["convert_html"] }}
- {{ translate["supports_links"] }}
### {{ translate["example_code"] }}
\`\`\`
print("¡Hola, Markdown!")
\`\`\`
Código Python para renderizarlo usando renderMarkdown:
Python
from lila.core.templates import renderMarkdown
@router.route(path='/markdown', methods=['GET'])
async def home(request: Request):
css = ["/public/css/styles.css"]
response = renderMarkdown(
request=request,
file='example',
css_files=css,
picocss=True
)
return response
Internacionalización (Traducciones)
Las traducciones se utilizan para internacionalizar una aplicación y mostrar contenido en
diferentes idiomas. En Lila, las traducciones se almacenan en el directorio app/locales
y pueden cargarse dinámicamente en la aplicación.
Forzar Idioma Predeterminado en las Rutas
El método render
acepta un parámetro lang_default
para forzar un idioma específico para la plantilla renderizada:
app.routes.py
# Forzar idioma español
@router.route(path="/es", methods=["GET"])
async def home(request: Request):
response = render(
request=request,
template="index",
lang_default="es" # Forzar traducciones al español
)
return response
# Forzar idioma inglés
@router.route(path="/en", methods=["GET"])
async def home(request: Request):
response = render(
request=request,
template="index",
lang_default="en" # Forzar traducciones al inglés
)
return response
Archivos de Traducción
Para cargar un archivo de localización, utiliza la función translate
de app.helpers.helpers.
Puedes acceder a las traducciones usando:
translate
- devuelve un diccionario con todas las traducciones
translate_
- devuelve una traducción específica (devuelve el texto original si no se encuentra)
Ejemplo de archivo de traducción (app/locales/translations.json):
app/locales/translations.json
{
"Send": {
"es": "Enviar",
"en": "Send"
},
"Cancel": {
"es": "Cancelar",
"en": "Cancel"
},
"Accept": {
"es": "Aceptar",
"en": "Accept"
},
"Email": {
"es": "Email",
"en": "Email"
},
"Name": {
"es": "Nombre",
"en": "Name"
},
"Back": {
"es": "Volver",
"en": "Back"
},
"Hi": {
"es": "Hola",
"en": "Hi"
}
}
Uso de Traducciones
Obtener una traducción específica:
example.py
from app.helpers.helpers import translate_
msg_error_login = translate_(
key="Incorrect email or password",
request=request,
file_name="guest"
)
Obtener todas las traducciones de un archivo:
example.py
from app.helpers.helpers import translate
all_translations = translate(
request=request,
file_name="guest"
)
Modelos (SQLAlchemy)
Los modelos se utilizan para definir la estructura de los
datos en la aplicación.
SQLAlchemy como el ORM
predeterminado para la gestión de bases de datos.
SQLAlchemy permite crear modelos de base de datos, ejecutar consultas y manejar
migraciones de manera eficiente.
Uso de Modelos
La clase `Base`, importada de `lila.core.database`, sirve como base para todos los
modelos.
Los modelos heredan de `Base` para definir tablas de base de datos con SQLAlchemy.
Ejemplo: Modelo de Usuario
Este ejemplo demuestra cómo crear un modelo `User` usando SQLAlchemy.
El modelo define una tabla `users` con columnas como `id`, `name`, `email`, `password`,
`token`, `active` y `created_at`.
app/models/user.py
from sqlalchemy import Table,Column,Integer,String,TIMESTAMP
from lila.core.database import Base
from app.connections import connection
class User(Base):
__tablename__='users'
id = Column(Integer, primary_key=True,autoincrement=True)
name=Column( String(length=50), nullable=False)
email=Column( String(length=50), unique=True)
password=Column(String(length=150), nullable=False)
token=Column(String(length=150), nullable=False)
active=Column( Integer, nullable=False,default=1)
created_at=Column( TIMESTAMP)
#Ejemplo de cómo usar SQLAlchemy para hacer consultas a la base de datos
def get_all(select: str = "id,email,name", limit: int = 1000) -> list:
query = f"SELECT {select} FROM users WHERE active =1 LIMIT {limit}"
result = connection.query(query=query,return_rows=True)#Devuelve filas
return result
#Ejemplo de cómo usar SQLAlchemy para hacer consultas a la base de datos
def get_by_id(id: int, select="id,email,name") -> dict:
query = f"SELECT {select} FROM users WHERE id = :id AND active = 1 LIMIT 1"
params = {"id": id}
row = connection.query(query=query, params=params,return_row=True)#Devuelve fila
return row
#Ejemplo usando abstracción ORM en SQLAlchemy
@classmethod
def get_all_orm(cls, db: Session, limit: int = 1000):
result = db.query(cls).filter(cls.active == 1).limit(limit).all()
return result
#Ejemplo de cómo usar la clase para hacer consultas a la base de datos
users = User.get_all()
user = User.get_by_id(1)
Para más detalles sobre SQLAlchemy, visita la documentación oficial:
Documentación de SQLAlchemy.
Generador de Modelos CLI
Genere automáticamente esqueletos de modelos de SQLAlchemy para agilizar su flujo de
trabajo de desarrollo.
El Generador de Modelos CLI es una potente herramienta de línea de comandos que crea
plantillas completas de modelos de SQLAlchemy con operaciones CRUD, métodos ORM y
ejemplos SQL sin procesar. Ahorra tiempo al proporcionar una estructura consistente
para todos sus modelos.
Comandos disponibles
Crear un nuevo modelo
Terminal
python -m cli.model create --name NombreModelo
# o con nombre de tabla personalizado
python -m cli.model create --name Producto --table products
Enumerar todos los modelos
Terminal
python -m cli.model list-models
Estructura del modelo generado
Cada modelo generado incluye columnas básicas (id, name, active, created_at) y
métodos CRUD completos.
El modelo también incluye comentarios con ejemplos de todos los tipos de columnas
disponibles.
app/models/product.py
from sqlalchemy import Column, Integer, String, TIMESTAMP, func
from sqlalchemy.orm import Session, load_only
from core.database import Base
from app.connections import connection
class Product(Base):
__tablename__ = "products"
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(length=100), nullable=False)
active = Column(Integer, nullable=False, default=1)
created_at = Column(TIMESTAMP, nullable=False, server_default=func.now())
@classmethod
def get_all(cls, select: str = "id,name", limit: int = 1000):
db = connection.get_session()
try:
column_names = [c.strip() for c in select.split(',')]
columns_to_load = [getattr(cls, c) for c in column_names]
result = db.query(cls).options(load_only(*columns_to_load)).filter(cls.active == 1).limit(limit).all()
return [{col: getattr(row, col) for col in column_names} for row in result]
finally:
db.close()
@classmethod
def get_by_id(cls, db: Session, id: int):
return db.query(cls).filter(cls.id == id, cls.active == 1).first()
@classmethod
def insert(cls, db: Session, params: dict) -> 'Product':
record = cls(name=params.get("name"), active=params.get("active", 1))
db.add(record)
return record
@classmethod
def update(cls, db: Session, id: int, params: dict) -> bool:
record = cls.get_by_id(db, id)
if record:
for key, value in params.items():
if hasattr(record, key):
setattr(record, key, value)
return True
return False
@classmethod
def delete(cls, db: Session, id: int) -> bool:
record = cls.get_by_id(db, id)
if record:
record.active = 0
return True
return False
Ejemplo de uso
# Obtener todos los productos
products = Product.get_all(select="id,name,price", limit=50)
# Obtener producto por ID
db = connection.get_session()
product = Product.get_by_id(db, 1)
# Insertar nuevo producto
new_product = Product.insert(db, {"name": "Laptop", "price": 999.99})
db.commit()
# Actualizar producto
Product.update(db, 1, {"name": "Gaming Laptop"})
db.commit()
# Eliminación temporal
Product.delete(db, 1)
db.commit()
db.close()
Flujo de trabajo completo
- Crear el modelo:
python -m cli.model create --name Producto - Edita
app/models/product.pypara agregar columnas personalizadas - Importa el modelo en
cli/migrations.py:from app.models.product import Product - Ejecuta migraciones:
python -m cli.migrations migrate - Bloqueo de IP: Bloquea direcciones IP que han sido marcadas por actividad maliciosa. Las IPs se bloquean por 6 horas por defecto.
- Bloqueo de URL: Bloquea URLs específicas que se sabe que son maliciosas o sospechosas.
-
Bloqueo de Rutas Sensibles:
Bloquea el acceso a rutas sensibles como
/etc/passwd,.env, y otras. -
Bloqueo de Extensiones Maliciosas:
Bloquea URLs que contienen extensiones de archivo maliciosas como
.php,.asp,.jsp, y.aspx. -
Detección de HTTP:
Bloquea solicitudes que contienen
"http"en los parámetros de consulta o en el contenido del cuerpo. blocked_ips.json: Almacena IPs bloqueadas con su tiempo de expiración.blocked_urls.json: Almacena URLs bloqueadas con su tiempo de expiración.sensitive_paths.json: Almacena una lista de rutas sensibles para bloquear.migrate: Ejecuta las migraciones de la base de datos--refresh: Flag opcional para eliminar y recrear todas las tablas- Genera rutas:
/login,/register,/forgot-password,/invalid-token,/change-password - Crea las páginas de
dashboardyprofile - Incluye middlewares, sesiones y validaciones usando Pydantic
- Actualiza automáticamente
main.pyycli/migrations.py - Templates: login, register, forgot-password, change-password
- Modelos: actualiza
Usery creaAuthModel - Rutas y funciones: manejadores para cada template/acción
- Middlewares: gestión de sesiones y validaciones
app/routes/{name}.py- Rutas CRUD completastemplates/html/{name}/index.html- DataTable con modales- Auto-importa rutas en
main.py - ✅ Valida que el modelo exista antes de generar
- ✅ DataTable responsive con búsqueda y paginación
- ✅ Modales de Crear y Editar
- ✅ Funcionalidad de borrado lógico (active=0)
- ✅ Ejemplos de validación Pydantic (comentados)
- ✅ Soporte multilenguaje
- ✅ Auto-importación en main.py
- Importa en
main.pyla ruta generada - Edita
app/routes/{name}.pypara agregar lógica de negocio - Descomenta los modelos Pydantic para validación
- Personaliza la plantilla HTML
- Agrega middleware personalizado
- vite_assets(): Inyecta las etiquetas de script necesarias. En modo
DEBUG=True, se conecta al servidor de desarrollo de Vite (HMR). En producción, carga los activos optimizados desdepublic/build. - react(component, props): Monta un componente de React específico.
- SEO con Jinja: El contenido de tu página principal es renderizado por Jinja, asegurando que los motores de búsqueda puedan leerlo perfectamente.
- Interactividad React: Usa React solo donde sea necesario (arquitectura de Islas).
- Sesiones Seguras: Puedes confiar en las sesiones seguras y firmadas de Lila (
itsdangerous) para la autenticación, mientras los componentes de React se comunican con tus puntos finales de API. - generate_html: bool = True – Genera automáticamente un CRUD en HTML usando un datatable responsive de Lila. Útil para generar la interfaz solo en desarrollo.
- rewrite_template: bool = False – Si es True, sobrescribe templates existentes.
- url_html: str = None – Opcional. Permite especificar la URL de salida del HTML. Si no se provee, el HTML puede copiarse y usarse en cualquier ruta.
- Autenticación: Inicio y cierre de sesión seguros para administradores.
- Gestión de Modelos: Genera automáticamente rutas y vistas para gestionar tus modelos.
- Métricas del Sistema: Supervisa el uso de memoria y CPU tanto para la aplicación como para el servidor.
- Gestión de Contraseñas: Permite a los administradores cambiar sus contraseñas.
- Registros: Permite a los administradores ver los
Logsde la aplicación. - Login:
/admin/login(GET/POST) - Logout:
/admin/logout(GET) - Dashboard:
/admin(GET) - Gestión de Modelos:
/admin/{model_plural}(GET) - Uso de memoria del framework Lila.
- Uso de CPU del framework Lila.
- Uso de memoria del sistema.
- Uso de CPU del sistema.
- Crea un script `.bat` o `.ps1` para iniciar tu aplicación.
- Abre el Programador de Tareas y crea una nueva tarea que se ejecute al inicio del sistema.
- Establece la acción para que ejecute tu script.
Middlewares
Las funciones de middleware se utilizan para interceptar solicitudes antes de que
lleguen a la lógica principal de la aplicación.
En Lila, los middlewares se definen en el directorio app/middlewares
(se puede modificar a cualquier archivo y/o directorio).
Los middlewares se pueden utilizar para tareas como autenticación, registro y manejo de
errores.
Por defecto, Lila viene con 3 middlewares para iniciar cualquier aplicación. Los
middlewares se pueden usar con decoradores @my_middleware.
login_required,
para validar que tienes una sesión firmada, para la clave 'auth' que se pasa como
parámetro para poder modificarla a tu gusto.
Si no se encuentra esta sesión, redirige a la URL que se pasa como parámetro, por
defecto, es "/login".
De lo contrario, continuará su curso ejecutando la ruta o función.
Luego tenemos session_active,
que se utiliza para verificar si tienes una sesión activa.
Redirigirá a la URL que se recibe como parámetro, por defecto, es "/dashboard".
El tercero es validate_token,
que se utiliza para validar un token JWT gracias a los helpers get_token
importados en from app.helpers.helpers import get_token.
from lila.core.session import Session
from lila.core.responses import RedirectResponse,JSONResponse
from lila.core.request import Request
from functools import wraps
from app.helpers.helpers import get_token
def login_required(func,key:str='auth',url_return='/login'):
@wraps(func)
async def wrapper(request, *args, **kwargs):
session_data= Session.unsign(key=key,request=request)
if not session_data:
return RedirectResponse(url=url_return)
return await func(request,*args,**kwargs)
return wrapper
def session_active(func,key:str='auth',url_return:str ='/dashboard'):
@wraps(func)
async def wrapper(request,*args,**kwargs):
session_data= Session.unsign(key=key,request=request)
if session_data:
return RedirectResponse(url=url_return)
return await func(request,*args,**kwargs)
return wrapper
def validate_token(func):
@wraps(func)
async def wrapper(request: Request, *args, **kwargs):
await check_token(request=request)
return await func(request, *args, **kwargs)
return wrapper
async def check_token(request:Request):
token = request.headers.get('Authorization')
if not token:
return JSONResponse({'session':False,'message': 'Invalid token'},status_code=401)
token = get_token(token=token)
if isinstance(token,JSONResponse):
return token
Aquí te damos varios ejemplos de cómo usar los 3, con los decoradores.
#Middleware para validar el Token JWT.
@router.route(path='/api/token', methods=['GET','POST'])
@validate_token #Middleware
async def api_token(request: Request):
"""Función Api Token"""
print(get_user_by_id_and_token(request=request))
return JSONResponse({'api': True})
#Middleware para validar sesión activa
@router.route(path='/dashboard', methods=['GET'])
@login_required #Middleware
async def dashboard(request: Request):
response = render(request=request, template='dashboard', files_translate=['authenticated'])
return response
#Middleware para validar si el usuario tiene sesión activa (si el usuario tiene sesión redirige a '/dashboard')
@router.route(path='/login', methods=['GET'])
@session_active #Middleware
async def login(request: Request):
response = render(request=request, template='auth/login', files_translate=['guest'])
return response
Middleware de Seguridad
Lila Framework incluye un ErrorHandlerMiddleware
incorporado que no solo maneja excepciones no controladas, sino que también proporciona
robustas comprobaciones de seguridad para proteger tu aplicación de solicitudes
maliciosas. Este middleware está diseñado para bloquear IPs, URLs y rutas sensibles
sospechosas, asegurando que tu aplicación permanezca segura.
Características
Configuración
El middleware utiliza tres archivos JSON ubicados en el directorio app/security:
Si estos archivos no existen, se crean automáticamente y se inicializan con valores predeterminados:
[
"/etc/passwd",
".env",
"wp-content",
"docker/.env",
"owa/auth/logon.aspx",
"containers/json",
"models",
"autodiscover/autodiscover.json",
"heapdump",
"actuator/heapdump",
"cgi-bin/vitogate.cgi",
"CFIDE/wizards/common/utils.cfc",
"var/www/html/.env",
"home/user/.muttrc",
"usr/local/spool/mail/root",
"etc/postfix/master.cf"
]
Uso
El ErrorHandlerMiddleware
se aplica automáticamente a todas las solicitudes. Puedes personalizar su comportamiento
modificando los archivos JSON en el directorio security.
from starlette.middleware.base import BaseHTTPMiddleware
from lila.core.responses import JSONResponse, HTMLResponse
from lila.core.request import Request
from lila.core.logger import Logger
from datetime import datetime, timedelta
import json
import os
def load_blocked_data(file_path, default_value):
try:
if not os.path.exists(file_path):
with open(file_path, "w") as file:
json.dump(default_value, file, indent=4)
return default_value
with open(file_path, "r") as file:
content = file.read().strip()
if not content:
with open(file_path, "w") as file:
json.dump(default_value, file, indent=4)
return default_value
try:
return json.loads(content)
except json.JSONDecodeError:
with open(file_path, "w") as file:
json.dump(default_value, file, indent=4)
return default_value
except Exception as e:
Logger.error(f"Error loading {file_path}: {str(e)}")
return default_value
def save_blocked_data(file_path, data):
try:
with open(file_path, "w") as file:
json.dump(data, file, indent=4)
except Exception as e:
Logger.error(f"Error saving {file_path}: {str(e)}")
async def is_blocked(blocked_data, key, request: Request):
if key in blocked_data:
expiration_time = datetime.fromisoformat(blocked_data[key]["expiration_time"])
if datetime.now() < expiration_time:
req = await Logger.request(request=request)
Logger.warning(f"Blocked: {key} \n {req}")
return True
return False
class ErrorHandlerMiddleware(BaseHTTPMiddleware):
def __init__(
self,
app,
blocked_ips_file="security/blocked_ips.json",
blocked_urls_file="security/blocked_urls.json",
sensitive_paths_file="security/sensitive_paths.json",
):
super().__init__(app)
self.blocked_ips_file = blocked_ips_file
self.blocked_urls_file = blocked_urls_file
self.sensitive_paths_file = sensitive_paths_file
self.blocked_ips = load_blocked_data(blocked_ips_file, default_value={})
self.blocked_urls = load_blocked_data(blocked_urls_file, default_value={})
self.sensitive_paths = load_blocked_data(sensitive_paths_file, default_value=[])
async def dispatch(self, request, call_next):
try:
client_ip = request.client.host
url_path = request.url.path
query_params = str(request.query_params)
body = await request.body()
if await is_blocked(self.blocked_ips, client_ip, request=request):
return HTMLResponse(
content="Access Denied
Your IP has been temporarily blocked.
",
status_code=403,
)
if await is_blocked(self.blocked_urls, url_path, request=request):
return HTMLResponse(
content="Access Denied
This URL has been temporarily blocked.
",
status_code=403,
)
malicious_extensions = [".php", ".asp", ".jsp", ".aspx"]
if any(ext in url_path for ext in malicious_extensions):
self.blocked_ips[client_ip] = {
"expiration_time": (datetime.now() + timedelta(hours=6)).isoformat()
}
save_blocked_data(self.blocked_ips_file, self.blocked_ips)
return HTMLResponse(
content="Access Denied
Malicious URL detected.
",
status_code=403,
)
if "http" in query_params or "http" in str(body):
self.blocked_ips[client_ip] = {
"expiration_time": (datetime.now() + timedelta(hours=6)).isoformat()
}
save_blocked_data(self.blocked_ips_file, self.blocked_ips)
return HTMLResponse(
content="Access Denied
Malicious query parameters detected.
",
status_code=403,
)
if any(path in url_path or path in str(body) for path in self.sensitive_paths):
self.blocked_ips[client_ip] = {
"expiration_time": (datetime.now() + timedelta(hours=6)).isoformat()
}
save_blocked_data(self.blocked_ips_file, self.blocked_ips)
return HTMLResponse(
content="Access Denied
Sensitive path detected.
",
status_code=403,
)
Logger.info(await Logger.request(request=request))
response = await call_next(request)
return response
except Exception as e:
Logger.error(f"Unhandled Error: {str(e)}")
return JSONResponse(
{"error": "Internal Server Error", "success": False}, status_code=500
)
Referencia de Comandos CLI
Lila Framework proporciona potentes herramientas CLI para acelerar el desarrollo.
Todos los comandos siguen
el patrón
python -m cli.{comando}
📦 Generador de Modelos
Crea modelos SQLAlchemy con métodos CRUD automáticamente.
# Crear un nuevo modelo
python -m cli.model create --name Product
# Crear con nombre de tabla personalizado
python -m cli.model create --name Product --table products
# Listar todos los modelos
python -m cli.model list-models
🏗️ Generador de Scaffold
Genera funcionalidad CRUD completa desde modelos existentes.
# Generar scaffold desde modelo
python -m cli.scaffold_crud --model Product
# Nombre de ruta personalizado
python -m cli.scaffold_crud --model Product --name products
🗄️ Migraciones
Ejecuta migraciones de base de datos para crear tablas desde modelos.
# Ejecutar migraciones
python -m cli.migrations migrate
# Refrescar (eliminar y recrear todas las tablas)
python -m cli.migrations migrate --refresh
🔐 Autenticación
Genera sistema de autenticación con login, registro y recuperación de contraseña.
# Generar sistema de autenticación
python -m cli.auth main
👤 Panel de Administración
Crea usuarios administradores y panel de administración.
# Crear usuario administrador
python -m cli.create_admin --password mypassword
# Crear panel de administración
python -m cli.create_panel_admin --password mypassword
⚡ Minificar Assets
Minifica archivos CSS y JavaScript para producción.
# Minificar todos los archivos CSS y JS
python -m cli.minify
⚛️ Integración con React
Configura entorno de desarrollo React con backend Lila.
# Instalar React y configurar en el proyecto
python -m cli.react create
# Iniciar servidor de desarrollo React
python -m cli.react dev
Conexiones a la base de datos
Para usar conexiones, necesitas importar la clase Database
de lila.core.database. Con eso puedes conectarte a tu base de datos, que puede ser
SQLite, MySQL, PostgreSQL o lo que quieras configurar.
A continuación te dejamos el ejemplo de cómo conectarte. La conexión se cerrará
automáticamente después de ser utilizada, por lo que puedes usarla como en este ejemplo
en la variable connection
from lila.core.database import Database
#SQLite
#Ejemplo de conexión a una base de datos sqlite
config = {"type":"sqlite","database":"test"} #test.db
connection = Database(config=config)
connection.connect()
#MySql
#Ejemplo de conexión a una base de datos mysql
config = {"type":"mysql","host":"127.0.0.1","user":"root","password":"password","database":"db_test","auto_commit":True}
connection = Database(config=config)
connection.connect()
mysql_connection = connection
Migraciones
En Lila Framework, las migraciones de bases de datos se pueden gestionar usando SQLAlchemy y la configuración de Lila para hacer las migraciones lo más fácil posible. El framework ahora soporta migraciones por línea de comandos a través de Typer, proporcionando una forma más intuitiva y flexible de gestionar tu esquema de base de datos.
Métodos de Migración
Hay dos formas principales de definir las tablas de la base de datos:
1. Usando Table
Este método define manualmente la estructura de la tabla usando el objeto Table de SQLAlchemy.
from sqlalchemy import Table, Column, Integer, String, TIMESTAMP
from app.connections import connection
import typer
import asyncio
app = typer.Typer()
# Ejemplo de creación de migraciones para 'users'
table_users = Table(
'users', connection.metadata,
Column('id', Integer, primary_key=True, autoincrement=True),
Column('name', String(length=50), nullable=False),
Column('email', String(length=50), unique=True),
Column('password', String(length=150), nullable=False),
Column('token', String(length=150), nullable=False),
Column('active', Integer, default=1, nullable=False),
Column('created_at', TIMESTAMP),
)
async def migrate_async(connection, refresh: bool = False) -> bool:
try:
if refresh:
connection.metadata.drop_all(connection.engine)
connection.prepare_migrate([table_users]) # para tablas
connection.migrate()
print("Migraciones completadas")
return True
except RuntimeError as e:
print(e)
return False
@app.command()
def migrate(refresh: bool = False):
"""Ejecutar migraciones de base de datos"""
success = asyncio.run(migrate_async(connection, refresh))
if not success:
raise typer.Exit(code=1)
if __name__ == "__main__":
app()
2. Usando Modelos (Recomendado)
Este enfoque define las tablas de la base de datos como clases de Python que heredan de "Base". Este es el método recomendado ya que proporciona más estructura y capacidades ORM.
from lila.core.database import Base
from sqlalchemy import Column, Integer, String, TIMESTAMP
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(length=50), nullable=False)
email = Column(String(length=50), unique=True)
password = Column(String(length=150), nullable=False)
token = Column(String(length=150), nullable=False)
active = Column(Integer, nullable=False, default=1)
created_at = Column(TIMESTAMP)
from app.connections import connection
from lila.core.database import Base # Importar Base para migraciones en modelos
from app.models.user import User # Importar modelos para migraciones
import typer
import asyncio
app = typer.Typer()
async def migrate_async(connection, refresh: bool = False) -> bool:
try:
if refresh:
connection.metadata.drop_all(connection.engine)
connection.migrate(use_base=True) # para modelos
print("Migraciones completadas")
return True
except RuntimeError as e:
print(e)
return False
@app.command()
def migrate(refresh: bool = False):
"""Ejecutar migraciones de base de datos"""
success = asyncio.run(migrate_async(connection, refresh))
if not success:
raise typer.Exit(code=1)
if __name__ == "__main__":
app()
Ejecutando Migraciones
Para ejecutar las migraciones, usa el siguiente comando en tu código:
# Migración básica
python -m cli.migrations
# Refrescar todas las tablas (eliminar y recrear)
python -m cli.migrations --refresh
Opciones de Comando
Nota: Cuando uses Modelos, asegúrate de importar todas tus clases de modelos en el archivo de migraciones para que SQLAlchemy pueda detectarlos para las migraciones.
Auth Scaffold
En Lila Framework, ahora puedes generar automáticamente todas las rutas, templates, modelos y middlewares de autenticación con un solo comando. Esto te permite configurar login, registro, recuperación de contraseña y dashboard sin crear archivos a mano.
Características
Ejecutando el Auth Scaffold
Usa el siguiente comando en tu terminal:
# Ejecuta el scaffold de auth para generar rutas, templates, modelos y middlewares
python -m cli.auth
Archivos y Estructura Generados
El scaffold creará/actualizará:
Generador de Scaffold CLI
Genera funcionalidad CRUD completa desde modelos existentes con un solo comando. El generador de scaffold crea rutas, plantillas y automáticamente importa todo en tu aplicación.
Uso Básico
Primero, crea tu modelo usando el CLI de modelos, luego genera el scaffold:
# Crear un modelo primero
python -m cli.model create --name Product
# Generar scaffold CRUD completo
python -m cli.scaffold_crud --model Product
# Nombre de ruta personalizado
python -m cli.scaffold_crud --model Product --name products
#Ejecutar migraciones con refresh
python -m cli.migrations --refresh
Qué se Genera
El generador de scaffold crea un sistema CRUD completo:
Endpoints Generados
Las siguientes rutas se crean automáticamente:
GET /product → Página HTML con DataTable
GET /api/product → Lista JSON de todos los productos
GET /api/product/{id} → Producto individual JSON
POST /api/product → Crear nuevo producto
PUT /api/product/{id} → Actualizar producto
DELETE /api/product/{id} → Borrado lógico (active=0)
Características
Personalización
Después de la generación, puedes personalizar:
Integración de Islas React
Lila ofrece una poderosa arquitectura de "Islas React", permitiéndote incrustar componentes interactivos de React directamente en tus plantillas Jinja2. Este enfoque combina lo mejor de ambos mundos: el SEO y el rendimiento del renderizado del lado del servidor con Lila, y la interactividad de React.
Instalación
Para configurar el entorno de React en tu proyecto desde el directorio raíz, ejecuta:
python -m cli.react create
Esto generará el esquema de package.json, vite.config.js y la estructura del directorio react/.
Uso
Una vez instalado, puedes usar los nuevos ayudantes de Jinja2 vite_assets() y react().
Ejemplo de una plantilla HTML completa:
<!DOCTYPE html>
<html lang="{{ lang }}">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="color-scheme" content="light dark" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<meta name="description" content="{{ description }}" />
<meta name="keywords" content="{{ keywords }}" />
<meta name="author" content="{{ author }}" />
<title>{{ title }}</title>
<link rel="icon" type="image/x-icon" href="{{ public('img/lila.png') }}" />
<link rel="stylesheet" href="{{ public('css/lila.css') }}" />
{{ vite_assets() | safe }}
</head>
<body>
<main class="mt-4 container">
<h1>Ejemplo de integración React</h1>
<div>{{ react('Counter', {'start': 3}) | safe }}</div>
</main>
</body>
</html>
Cómo Funciona
Desarrollo (DEBUG=True):
El ayudante vite_assets() inyecta scripts apuntando a http://localhost:5173.
Puedes iniciar el servidor de desarrollo con:
npm run dev
# O
python -m cli.react dev
Producción (DEBUG=False):
Ejecuta npm run build para compilar tu código React en public/build.
El ayudante detectará automáticamente el archivo manifest.json y cargará los archivos CSS y JS hasheados para un rendimiento óptimo.
Beneficios
React Render (componentes de renderizado con htmlResponse sin plantillas jinja)
Este método reactRender se utiliza para devolver el HTML directamente y cargar una página o componente de React con estilos, metaetiquetas, scripts personalizados y contenido/props.
from lila.core.templates import render,renderMarkdown,renderReact
...
router = Router()
@router.get("/react-page")
async def react_page(request: Request):
response = renderReact(component="Counter",
props={
"start": 5
} ,
options={
"title": "React Page",
"meta": [{
"name": "description",
"content": "A React page with a counter component"
}],
"styles": ["/public/css/lila.css"]
}
)
return response
Generación Simple de CRUD de API REST
En Lila, tenemos una forma sencilla de generar CRUDs con documentación automática, permitiéndote crear tu API REST de manera eficiente.
Gracias a la combinación de modelos de SQLAlchemy y Pydantic, es posible realizar validaciones de datos y ejecutar consultas estructuradas para la generación de la API.
Además, puedes integrar middlewares personalizados para validar tokens, gestionar sesiones o procesar solicitudes. Con solo unas pocas líneas de código, puedes generar un CRUD de API REST completamente documentado.
Si aún no lo has hecho, habilita las migraciones cuando inicies el
servidor.
Por defecto, utiliza SQLite, creará un archivo de base de datos lila.sqlite
en la raíz del proyecto.
python -m cli.migrations
from lila.core.request import Request
from lila.core.responses import JSONResponse
from lila.core.routing import Router
from pydantic import EmailStr, BaseModel
from app.middlewares.middlewares import validate_token, check_token, check_session
from app.connections import connection # Conexión a la base de datos con SQLAlchemy
from app.models.user import User # Modelo 'User' de SQLAlchemy
router = Router()# Inicializa la instancia del enrutador para gestionar las rutas de la API.
# Modelo Pydantic para validaciones al crear o modificar un usuario.
class UserModel(BaseModel):
email: EmailStr
name: str
token: str
password: str
# Definiciones de middlewares para operaciones CRUD
middlewares_user = {
"get": [],
"post": [],
"get_id": [],
"put": [],
"delete": [check_session, check_token],#Ejemplo de middlewares para sesión web 'check_session' y jwt 'check_token'
}
# Generar automáticamente CRUD con validaciones y configuraciones
router.rest_crud_generate(
connection=connection, # Conexión a la base de datos
model_sql=User, # Modelo SQLAlchemy
model_pydantic=UserModel, # Modelo Pydantic
select=["name", "email", "id", "created_at", "active"], # Campos a seleccionar en las consultas
delete_logic=True, # Habilita el borrado lógico (actualiza 'active = 0' en lugar de eliminar registros)
active=True, # Filtra automáticamente los registros activos ('active = 1')
middlewares=middlewares_user, # Middlewares personalizados para cada acción CRUD
)
Puedes crear tus propios middlewares y pasarlos como una lista para
personalizar la seguridad y las validaciones para cada operación de rest_crud_generate.
Para generar la documentación, recuerda siempre ejecutarla después de las rutas
router.swagger_ui()
y router.openapi_json()
Parámetros de la Función para rest_crud_generate
A continuación se muestran los parámetros que acepta esta función para generar CRUD automáticamente:
def rest_crud_generate(
self,
connection,
model_sql,
model_pydantic: Type[BaseModel],
select: Optional[List[str]] = None,
columns: Optional[List[str]] = None,
active: bool = False,
delete_logic: bool = False,
middlewares: dict = None,
jsonresponse_prefix:str='',#Devolver con prefijo de lista o dict 'data' como primera clave
user_id_session:bool| str=False #Ejemplo 'user id' para validar en la consulta con where 'user_id'= id session_user
) :
Documentación Automática
A continuación se muestra un ejemplo de la documentación generada para la función rest_crud_generate:
Ve a http://127.0.0.1:8000/docs,
o como esté configurado en tu archivo .env (por HOST y PORT).
GET - Obtener todos los usuarios
GET - Salida de todos los usuarios
POST - Crear nuevo usuario
GET_ID - Obtener un usuario específico
PUT - Actualizar usuario
DELETE - Eliminar usuario
En este ejemplo, lo hicimos con 'users', pero puedes aplicarlo como quieras según tu
lógica, 'products', 'stores', etc. Incluso modificando el núcleo en core/routing.py.
Para la función de método 'POST' o 'PUT', Si el framework detecta que pasas datos en el cuerpo de la solicitud como: 'password', lo codificará automáticamente con argon2 para hacerlo seguro. Ejemplo de cuerpo:
{
"email":"example@example.com",
"name":"name",
"password":"my_password_secret"
}
Luego, si pasas 'token' o 'hash', con la función helper
generate_token_value
, genera automáticamente un token, que se guardará en la base de datos como la columna
'token' con el valor generado por la función.
Ejemplo de cuerpo:
{
"email":"example@example.com",
"name":"name",
"password":"my_password_secret",
"token":""
}
Con 'created_at' o 'created_date', guardará la fecha y hora del momento, siempre que ese campo exista en la tabla de la base de datos. Ejemplo de cuerpo:
{
"email":"example@example.com",
"name":"name",
"password":"my_password_secret",
"token":"",
"created_at":""
}
Para los métodos 'PUT', 'GET' (get_id) o 'DELETE'
Es opcional según la lógica de cada API REST, puedes pasarlo como query string,
user_id
o id_user,
un ejemplo sería GET, PUT o DELETE como método a la url
http://127.0.0.1:8000/api/products/1?user_id=20
Donde valida que el ID del producto '1' existe pero también que pertenece al ID de usuario '20'.
Nuevos Parámetros para Generación de CRUD en HTML
Además de generar el CRUD de la API REST, rest_crud_generate ahora acepta tres nuevos parámetros para
crear un CRUD en HTML completamente funcional:
Esta funcionalidad permite generar CRUDs con create, update, delete, paginación, validaciones y utiliza el mismo REST CRUD API como backend. Es ideal para crear rápidamente múltiples CRUDs en HTML y backend para tu aplicación: con aproximadamente 30 líneas de código, ya tienes un CRUD en HTML funcional listo para pequeños ajustes o incluso listo para usar tal cual.
Puedes pasar DEBUG desde from app.config import DEBUG a generate_html para que solo se genere en desarrollo.
Si pasas generate_html=False, el HTML no se generará
automáticamente y puedes usarlo como desees.
Panel de Administración
El módulo Admin
te permite gestionar un panel de administración para tu aplicación.
Incluye autenticación, gestión de modelos, métricas del sistema y más. Este panel es
altamente personalizable y se integra fácilmente con tu aplicación.
El panel de administración ahora es más modular y flexible. Todos los componentes
relacionados con la administración (plantillas, rutas y configuración)
se encuentran en la carpeta admin,
lo que facilita su personalización y extensión.
Características Clave
Uso Básico
Para usar el panel de administración, necesitas importar la clase Admin
de admin.routes
y pasarle una lista opcional de modelos que quieres gestionar. Cada modelo debe
implementar un método get_all
para ser mostrado en el panel de administración.
# English: Here we activate the admin panel with default settings.
# Español: Aquí activamos el panel de administrador con configuraciones predeterminadas.
from app.routes.admin import Admin
from app.models.user import User
admin_routes=Admin(models=[User])
all_routes = list(itertools.chain(routes, api_routes,admin_routes))
Configuración y Migraciones
Antes de usar el panel de administración, necesitas ejecutar las migraciones:
python -m cli.migrations
Creación de Usuarios Administradores
Los usuarios administradores se pueden crear a través de la línea de comandos con parámetros personalizables:
# Uso predeterminado (se genera una contraseña aleatoria)
python -m cli.create_panel_admin
# Con nombre de usuario y contraseña personalizados
python -m cli.create_panel_admin --user miadmin --password misecretopassword
Rutas Generadas
El panel de administración genera automáticamente las siguientes rutas:
Puedes acceder a las plantillas en la carpeta templates/html/admin.
Middleware de Autenticación
Para proteger las rutas y asegurar que solo los administradores autenticados puedan
acceder a ellas, usa el decorador @admin_required
de core/admin.py.
from lila.core.admin import admin_required
@router.route(path="/admin", methods=["GET"])
@admin_required
async def admin_route(request: Request):
menu_html = menu(models=models)
return await admin_dashboard(request=request, menu=menu_html)
Métricas del Sistema
El panel de administración muestra métricas en tiempo real, incluyendo:
Estas métricas se actualizan cada 10 segundos.
Registros
En Lila, usamos un middleware que puedes habilitar o deshabilitar si quieres usar Logs
para información, advertencias o errores en tu aplicación.
El middleware se encuentra en core/middleware.py
y se agrega a la aplicación con: app.add_middleware(ErrorHandlerMiddleware).
Esto ayuda a generar registros que puedes ver en el panel de administración, organizados por fecha (en carpetas) y tipo.
Creación de Usuarios Administradores (Comando Simple)
Además del comando para crear el panel completo, ahora puedes crear administradores individuales sin afectar las rutas, middlewares o configuraciones existentes. Este comando es útil para añadir nuevos usuarios administradores de manera rápida.
# Crear un administrador con nombre de usuario y contraseña específicos
python -m cli.create_admin --username miadmin --password misecretopassword
# Crear un administrador con usuario personalizado y contraseña generada automáticamente
python -m cli.create_admin --username miadmin
Nota: Este comando solo agrega el usuario administrador en la base de datos. No genera rutas, middlewares ni plantillas, por lo que se puede usar incluso si el panel ya está activo o personalizado.
Minificar archivos para producción
Con el comando lila-minify
puedes minificar tus archivos js, css , que se encuentran en las carpetas public
y templates.
lila-minify
#O
python -m cli.minify
Despliegue a Producción
Servidor Linux (con systemd)
Para desplegar tu aplicación Lila en un servidor Linux, se recomienda usar un entorno virtual y un servicio systemd para gestionar el proceso.
1. Crear un Entorno Virtual
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
2. Crear un Script de Inicio
#!/bin/bash
source /ruta/a/tu/proyecto/venv/bin/activate
python /ruta/a/tu/proyecto/main.py
3. Crear un Servicio systemd
[Unit]
Description=Servicio de Lila Framework
After=network.target
[Service]
User=tu_usuario
Group=tu_grupo
WorkingDirectory=/ruta/a/tu/proyecto
ExecStart=/ruta/a/tu/proyecto/start_lila.sh
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
4. Habilitar e Iniciar el Servicio
sudo systemctl daemon-reload
sudo systemctl enable lila.service
sudo systemctl start lila.service
chmod +x /path/your_project/start_lila.sh
Servidor Windows
En Windows, puedes usar el Programador de Tareas para ejecutar tu aplicación al inicio.
Desactivar el Modo de Depuración
En tu archivo `.env`, asegúrate de establecer `DEBUG=False` para producción.
DEBUG=False