Instalación
$ source venv/bin/activate # En Windows puedes usar `venv\Scripts\activate`
$ pip install lila-framework
$ lila-init
$ python main.py #O python3 main.py
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, pero puede ser donde tu quieras) y se importan en
main.py
para su uso. Las rutas pueden ser
configuradas para manejar solicitudes HTTP, métodos de API, y más.
Además de JSONResponse puedes utilizar HTMLResponse,
RedirectResponse y PlainTextResponse, o
StreamingResponse , Para transmitir datos en tiempo
real (útil para streaming de video/audio o
respuestas grandes).
A continuación, se muestra un
ejemplo de cómo se definen las rutas en Lila:
routes/api.py
#Importar para las respuestas JSONResponse .
from lila.core.responses import JSONResponse
#Administra las rutas para los puntos finales de la API.
from lila.core.routing import Router
#Inicializa la instancia del enrutador para manejar rutas de la API.
router = Router()
# Define una ruta de API simple que soporta el método GET.
@router.route(path='/api', methods=['GET'])
async def api(request: Request):
"""Api function"""
#Español: Devuelve una respuesta JSON simple para la verificación de la API.
return JSONResponse({'api': True})
Recibir parametros por GET
En esta función, recibimos un parámetro a través de la URL usando {param}
.
Si el
parámetro no se envía, se asigna el valor por defecto 'default'
. La
respuesta es un
JSON con el valor recibido.
Recepción de 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})
O también puedes hacerlo de esta manera
Receiving GET Query parameters
@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_params})
Imports básicos
from lila.core.responses import JSONResponse # Simplifica el envío de respuestas JSON.
from lila.core.routing import Router # Administra las rutas de la API.
from lila.core.request import Request # Maneja solicitudes HTTP en la aplicación.
from pydantic import EmailStr, BaseModel # Valida y analiza modelos de datos para la validación de entradas.
from app.helpers.helpers import get_user_by_id_and_token
from app.middlewares.middlewares import validate_token
router = Router()
Uso de Middlewares y Decoradores
Los middlewares permiten interceptar 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 header
de
la solicitud.
Validación con Middleware
@router.route(path='/api/token', methods=['GET','POST'])
@validate_token # Middleware para validar el 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,
al especificar un modelo en la ruta, se genera documentación automática en
/docs
.
Validación con Pydantic
from pydantic import EmailStr, BaseModel
class ExampleModel(BaseModel):
email: EmailStr # Garantiza que el email es válido.
password: str # Cadena de texto para la 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"Invalid JSON Body: {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 se puede generar un archivo JSON de OpenAPI
para
herramientas externas.
Generación de Documentación
router.swagger_ui() # Habilita Swagger UI para la documentación de la API.
router.openapi_json() # Genera JSON de OpenAPI para herramientas externas.
Importación de Rutas en main.py
Para usar las rutas definidas en el router, es necesario obtenerlas con
router.get_routes()
e importarlas en main.py
.
Importación de Rutas
routes = router.get_routes() # Obtiene todas las rutas definidas.
Archivos estáticos
Para cargar archivos estáticos(js,css,etc) se puede utilizar el método
mount()
.
Qué se recibirá
como parámetros que se intercambian por defecto.
path: str = '/public', directory: str = 'static', name: str = 'static'
Estático
from lila.core.routing import Router
# Crear una instancia de Router para definir las rutas, si no se creó previamente en el archivo
router = Router()
# Montar los archivos estáticos en la carpeta 'static', url = '/public' por defecto
router.mount()
Renderizado de Plantillas (Jinja2 + Lila JS)
En Lila puedes usar Jinja2
para renderizar HTML con datos del servidor o Lila JS
para crear componentes reactivos en el cliente, o incluso combinar ambos enfoques según tus necesidades.
Renderizado con Jinja2
En Lila, Jinja2 se usa por defecto para renderizar plantillas HTML y enviarlas al cliente.
Con context
, puedes pasar información como traducciones, datos, valores, listas o diccionarios.
📌 Uso básico en rutas
@router.route(path="/", methods=["GET"])
async def home(request: Request):
context = {}
response = render(
request=request,
template="index",
context=context
)
# English: Renders the 'index' template with translations
# Español: Renderiza la plantilla 'index' con traducciones
return response
📌 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: objeto
Request
.
- template (str): nombre de la plantilla (sin extensión
.html
).
- context (dict): datos a pasar a la plantilla (diccionarios, listas, traducciones, etc.).
- theme_ (bool, default=True): aplica el tema visual por defecto.
- translate (bool, default=True): activa el sistema de traducciones integrado.
- files_translate (list): lista de archivos de traducción adicionales.
- lang_default (str): idioma por defecto forzado (ideal para SEO en rutas como
/es
, /en
, /fr
).
- templates: colección de plantillas base usada por Lila.
En Lila, además puedes usar translate
para pasar traducciones a Jinja2,
y parámetros como title
o version
(ideal para estructurar APIs: "v1", "v2").
Lila JS: Sistema Reactivo
Lila JS es una librería minimalista para crear interfaces reactivas con JavaScript puro.
Conceptos Clave
Estado Reactivo
El estado es un objeto especial que actualiza la interfaz automáticamente al cambiar.
state: () => ({
email: '',
password: ''
})
Enlace de Datos Bidireccional
Vinculación entre inputs y estado usando data-model
.
<input data-model="email">
<span data-bind="email"></span>
Componentes
Unidades reutilizables con su propio estado, plantilla y acciones.
App.createComponent('login-component', {
template: 'login-template',
state: () => ({...}),
actions: {...}
});
Ruteo
Navegación en cliente entre componentes.
App.defineRoute('/login', '<login-component></login-component>');
App.mount('app'); // id del div donde se montará
Ventajas de Este Enfoque
🚀 Rendimiento
Combina la velocidad inicial de Jinja2 con la fluidez de una SPA.
🔍 SEO Optimizado
El contenido crítico se renderiza en el servidor para buscadores.
💡 Progresivo
Comienza con Jinja2 y añade interactividad solo donde se necesite.
🛠️ Sencillo
Sin configuraciones complejas ni dependencias pesadas.
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 la 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.
Estableciendo Datos de Sesión
El método setSession
se utiliza para establecer datos de sesión en una cookie. Puede manejar cadenas, diccionarios y listas, que se serializan automáticamente a JSON si es necesario.
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.
Obteniendo Datos de Sesión
El método getSession
recupera los datos de sesión brutos y firmados de una cookie. Para obtener los datos de sesión sin firmar y validados, debe usar getSessionValue
o unsign
.
Verificando y Validando Datos de Sesión
Los métodos unsign
y getSessionValue
se utilizan para obtener los datos de la sesión en su forma original después de verificar la firma. Se recomienda getSessionValue
ya que también maneja las firmas caducadas con gracia.
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ó la sesión o la sesión no es válida o ha caducado."}, status_code=401)
Eliminando 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
Helper para Subida de Archivos
El helper upload
proporciona una solución completa para manejar subidas
de archivos en tu aplicación,
incluyendo validación, controles de seguridad y soporte para traducciones.
Este helper maneja automáticamente:
- Validación del método HTTP (solo POST)
- Validación del Content-Type (multipart/form-data)
- Validación de extensiones de 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 para traducción de mensajes de error
Parámetros
La función upload
acepta estos parámetros:
Parámetros
request: Request # Objeto request de Starlette
name_file: str | list # Nombre del campo para subida (por defecto: 'file')
UPLOAD_DIR: str # Directorio para guardar archivos (por defecto: 'uploads')
ALLOWED_EXTENSIONS: set # Extensiones permitidas (por defecto: {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'})
MAX_FILE_SIZE: int # Tamaño máximo en bytes (por defecto: 10MB)
Ejemplo de Uso
Implementación en 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 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 correctamente!');
} catch (error) {
alert(error.message);
}
}
</script>
Formato de Respuesta
El helper retorna un JSONResponse con esta estructura:
Respuesta Exitosa
{
"file": "/uploads/nombre_archivo.ext",
"success": true,
"message": "Archivo subido correctamente"
}
Respuesta de Error
{
"error": true,
"success": false,
"message": "Mensaje de error en el idioma del usuario"
}
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, comenzando desde la base_path
del
directorio
ubicado en templates/markdown/
, por ejemplo: index.md
.
Buscará un archivo index.md
en el directorio de Markdown dentro de la
carpeta de
plantillas
(templates/markdown/index.md).
css_files
y js_files
son listas de archivos CSS y JS que se
cargarán
en el
archivo HTML generado.
picocss
es un valor booleano que indica si se debe cargar el archivo CSS
PicoCSS.
A continuación, se muestra un ejemplo de cómo se representan los archivos
markdown
en
Lilac:
Markdown
from lila.core.templates import renderMarkdown
@router.route(path='/markdown', methods=['GET'])
async def home(request: Request):
#Define una lista de archivos CSS para incluir en la respuesta
css = ["/public/css/styles.css"]
#Representa un archivo markdown con estilo PicoCSS
response = renderMarkdown(request=request, file='example', css_files=css, picocss=True)
return response
Internalizació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 se pueden cargar dinámicamente en la aplicación.
Forzar idioma por defecto en rutas
El método render
acepta un parámetro lang_default
para forzar
un idioma específico en 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" # Fuerza traducciones en 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" # Fuerza traducciones en inglés
)
return response
Archivos de traducción
Para cargar un archivo de locales, usa la función translate
desde
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"
}
}
Usando 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"
)
Configuración adicional
También puedes utilizar el helper lang
para obtener el idioma actual basado
en la sesión o configuración de la aplicación:
example.py
from app.helpers.helpers import lang
current_language = lang(request)
Modelos (SQLAlchemy)
Los modelos se utilizan para definir la estructura de los datos en la aplicación.
SQLAlchemy es 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 desde `lila.core.database`, sirve como base para todos los
modelos.
Los modelos heredan de `Base` para definir las tablas de la base de datos con
SQLAlchemy.
Ejemplo: Modelo de Usuario
Este ejemplo muestra cómo crear un modelo `User` utilizando 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 sqlalchemy.orm import Session
from lila.core.database import Base
from app.connections import connection
from argon2 import PasswordHasher
ph = PasswordHasher()
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 como poder utilizar 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)#Retornar todos los elementos
return result
# Ejemplo de como poder utilizar 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)#Retorna un elemento
return row
#Ejemplo usando abstracción de 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 como usar la clase para realizar 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.
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
(puede
modificarse a cualquier archivo y/o directorio).
Los middlewares se pueden utilizar para tareas como autenticación, registro y manejo de
errores.
Por defecto, Lila incluye 3 middlewares para iniciar cualquier aplicación. Los
middlewares se pueden utilizar con decoradores @my_middleware
.
login_required
, para validar que tengas una sesión firmada, para la clave
'auth' que se pasa como parámetro para poder modificarla como desees.
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
.
app/middlewares/middlewares.py
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': 'Token inválido'}, 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.
Middlewares en rutas
# 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
integrado que no solo
maneja excepciones no controladas, sino que también proporciona verificaciones de
seguridad robustas para proteger tu aplicación de solicitudes maliciosas. Este
middleware está diseñado para bloquear IPs sospechosas, URLs y rutas sensibles,
asegurando que tu aplicación permanezca segura.
Características
-
Bloqueo de IPs:
Bloquea direcciones IP que han sido marcadas por actividad maliciosa. Las IPs se
bloquean durante 6 horas por defecto.
-
Bloqueo de URLs:
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
.
Dado que no ejecutamos dichas extensiones de los lenguajes mencionados, se bloquean
para evitar
posibles ataques.
-
Detección de HTTP:
Bloquea solicitudes que contienen
"http"
en los parámetros de consulta
o en el contenido del cuerpo.
Configuración
El middleware utiliza tres archivos JSON ubicados en el directorio
app/security
:
blocked_ips.json
: Almacena las IPs bloqueadas con su tiempo de
expiración.
blocked_urls.json
: Almacena las URLs bloqueadas con su tiempo de
expiración.
sensitive_paths.json
: Almacena una lista de rutas sensibles para
bloquear.
Si estos archivos no existen, se crean automáticamente y se inicializan con valores
predeterminados:
security/sensitive_paths.json
[
"/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
.
core/middleware.py
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 cargando {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 guardando {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"Bloqueado: {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="Acceso Denegado
Tu IP ha sido bloqueada temporalmente.
",
status_code=403,
)
if await is_blocked(self.blocked_urls, url_path, request=request):
return HTMLResponse(
content="Acceso Denegado
Esta URL ha sido bloqueada temporalmente.
",
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="Acceso Denegado
Se detectó una URL maliciosa.
",
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="Acceso Denegado
Se detectaron parámetros de consulta maliciosos.
",
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="Acceso Denegado
Se detectó una ruta sensible.
",
status_code=403,
)
Logger.info(await Logger.request(request=request))
response = await call_next(request)
return response
except Exception as e:
Logger.error(f"Error no controlado: {str(e)}")
return JSONResponse(
{"error": "Error interno del servidor", "success": False}, status_code=500
)
Conexiones a la base de datos
Para utilizar conexiones, necesitas importar la clase Database
desde
lila.core.database.
Con
eso podrás conectarte a tu base de datos, que puede ser SQLite, MySLQ, PostgreSQL o la
que
quieras configurar.
A continuación te dejamos el ejemplo de cómo conectarte. La conexión se cerrará
automáticamente
luego
de ser utilizada, por lo que puedes utilizarla como en este ejemplo en la variable
connection
app/connections.py
from lila.core.database import Database
#SQLite
config = {"type":"sqlite","database":"test"} #test.db
connection = Database(config=config)
connection.connect()
#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 base de datos se pueden gestionar usando SQLAlchemy
y la configuración de Lila
para hacer las migraciones lo más sencillas 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 el esquema de tu base de
datos.
Métodos de Migración
Existen dos formas principales de definir tablas de base de datos:
1. Usando Table
Este método define manualmente la estructura de la tabla usando el objeto Table de
SQLAlchemy.
cli/migrations.py
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):
"""Ejecuta las migraciones de la base de datos"""
success = asyncio.run(migrate_async(connection, refresh))
if not success:
raise typer.Exit(code=1)
if __name__ == "__main__":
app()
2. Usando Models (Recomendado)
Este enfoque define las tablas de la base de datos como clases Python que heredan de
"Base".
Este es el método recomendado ya que proporciona más estructura y capacidades ORM.
app/models/user.py
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)
cli/migrations.py (versión con Models)
from app.connections import connection
from lila.core.database import Base # Importar Base para migraciones con modelos
from app.models.user import User # Importar modelos para las 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):
"""Ejecuta las migraciones de la 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 terminal:
Comando de Terminal
# Migración básica
python -m cli.migrations
# Refrescar todas las tablas (eliminar y recrear)
python -m cli.migrations --refresh
Opciones del Comando
migrate
: Ejecuta las migraciones de la base de datos
--refresh
: Opcional, elimina y recrea todas las tablas
Nota: Cuando uses Models, asegúrate de importar todas tus clases de
modelo en el archivo de migraciones
para que SQLAlchemy pueda detectarlas para las migraciones.
Integración de React con Lila
Lila proporciona una integración fluida con React, permitiéndote desarrollar tu frontend
con una
configuración modificada de Vite + React, manteniendo fuertes capacidades de SEO, caché
e hidratación
de HTML. Esta configuración asegura que tu aplicación React pueda prerenderizarse y ser
reconocida
por Google Bots y sistemas de IA.
Iniciando un Proyecto React en Lila
Para crear o ejecutar un proyecto React dentro de Lila, ejecuta el siguiente comando:
Comando CLI
# Para entornos Python 3
python3 -m cli.react [--name NOMBRE_DEL_PROYECTO]
# O si tu Python por defecto es python
python -m cli.react [--name NOMBRE_DEL_PROYECTO]
Parámetros:
--name
: Nombre opcional del proyecto, por defecto "react"
.
Configuración CORS para Desarrollo
Al ejecutar React en modo desarrollo, Lila edita automáticamente main.py
para agregar la
configuración CORS necesaria. Por defecto:
Fragmento de CORS en main.py
# Inglés: for development react
# Español: para desarrollo react
cors = {
"origin": ["http://localhost:5173"],
"allow_credentials": True,
"allow_methods": ["*"],
"allow_headers": ["*"]
}
Rutas Automáticas y Montaje de Assets
Lila modifica app/routes/routes.py
automáticamente para tu build de React.
Esto asegura
que los assets del build se sirvan correctamente y que index.html
se
renderice desde la
caché.
Ejemplo de routes.py
router.mount(path="/assets", directory="templates/html/react/assets", name="react-assets")
@router.route(path="/{path:path}", methods=["GET"])
async def home(request: Request):
response = render(request=request, template="react/index")
return response
Recuerda modificar en el archivo main.py
, cuando vayas a pasar tus rutas
para inicializar
la aplicación , pasa siempre primero api_routes o como tu le hayas
puesto.
Esto es clave para que tome tus rutas de api, sin colisionar con la ruta general
de React
@router.route(path="/{path:path}", methods=["GET"])
, donde React , con React Router o lo que utilices pueda manejar todas las rutas .
Quedando de esta forma en main.py
main.py
all_routes = list(itertools.chain( api_routes,routes))
#Primero api_routes, luego routes (donde esta la ruta general de React)
Prerender y Caché
Para mejorar el SEO, Lila soporta prerenderizar tus páginas React y guardarlas en caché.
Después de
ejecutar npm run build
, puedes generar el HTML en caché:
Generar Caché
python -m cli.prerender --url http://localhost:8001
# o python3 -m cli.prerender
Luego, en tu archivo de desarrollo react/index.html
, incluye el HTML en
caché para la
hidratación y SEO:
Fragmento index.html
{% include "react/cache/index.html" ignore missing %}
Integración de Prerender en main.py
Opcionalmente, puedes ejecutar el prerender automáticamente al iniciar el servidor
descomentando
la siguiente sección en main.py
:
main.py prerender al inicio
# import subprocess
# import sys
# @app.on_event("startup")
# async def startup_event():
# print("♻️ Prerender para React...")
# url_with_port = f"http://{HOST}:{PORT}"
# subprocess.Popen([
# sys.executable,
# "-m",
# "cli.prerender",
# "--url",
# url_with_port.strip()
# ])
SEO e Hidratación
Al incluir el HTML en caché dentro del div
con id root
, React
rehidrata
el contenido del lado del cliente. Esto permite:
- Fuerte reconocimiento SEO por Google Bots y sistemas de IA.
- Pintado inicial más rápido con HTML prerenderizado.
- Caché diario opcional usando una tarea programada de prerender.
Helpers y Contexto para React
Puedes pasar contexto desde plantillas Jinja2 a React usando etiquetas
<script>
en templates/html/react/index.html
. Útil para traducciones, configuración o
cualquier
dato dinámico.
Documentación Automática de API
Lila genera automáticamente la documentación de API usando modelos Pydantic. Todos los
helpers
y middlewares se pueden combinar perfectamente con las rutas React.
Validación de Sesión con React
En el backend, puedes agregar un middleware para validar la sesión. También puedes usar JWT, pero la Sesión es más segura porque está firmada por itsdangerous
.
De esta forma, puedes validar peticiones y guardar IDs públicos, tokens, etc., para validar sesiones, o puedes usar JWT o ambos.
La recomendación principal es validar la sesión de esta manera o con login_required
, que es otro middleware que también se puede configurar allí.
De esta forma, Lila y React quedan conectados para el desarrollador. En producción, gracias a la condición if DEBUG
, será seguro con la firma de itsdangerous
en la clase Session
.
app/routes/api.py
from app.middlewares.middlewares import login_required_auth, check_token, check_session
from lila.core.session import Session
from app.config import DEBUG
# Español: Define una ruta de API simple que soporta el método GET.
@router.route(path="/api", methods=["GET"])
async def api(request: Request):
"""Api function"""
# Español: Devuelve una respuesta JSON simple para la verificación de la API.
response = JSONResponse({"api": True})
new_val={"id":20}
samesite="none" if DEBUG else "secure"
Session.setSession(new_val=new_val,name_cookie='auth',response=response,samesite=samesite)
return response
# Español: Define una ruta de API que soporta los métodos GET y POST.
@router.route(path="/api/token", methods=["GET", "POST"])
# Español: Middleware para validar token de JWT.
@login_required_auth
async def api_token(request: Request):
"""Api Token function"""
print(Session.getSessionValue(request=request))
return JSONResponse({"api": True})
app/middlewares/middlewares.py
def login_required_auth(func, key: str = "auth"):
@wraps(func)
async def wrapper(request, *args, **kwargs):
session_data = Session.getSession(request=request,key=key)
if not session_data:
return JSONResponse({"session": False, "success": False}, status_code=401)
return await func(request, *args, **kwargs)
Componente de React
import { useEffect } from 'react';
function MyComponent() {
useEffect(() => {
const start = async () => {
const resp = await fetch('http://127.0.0.1:8000/api', { credentials: 'include' });
const resp_ = await fetch('http://1227.0.0.1:8000/api/token', { credentials: 'include' });
const json = await resp_.json();
console.log(json);
};
start();
}, []);
return (
<div>
{/* Tu JSX de componente */}
</div>
);
}
Generación sencilla de CRUD de API Rest
En Lila tenemos una forma sencilla para generar CRUDs con documentación automática,
permitiéndote crear tu API Rest de manera eficiente.
Gracias a la combinación de los modelos de SQLAlchemy y
Pydantic, es posible realizar validaciones de datos y ejecutar
consultas de
manera estructurada para la generación de la API.
Además, puedes integrar middlewares personalizados para validar tokens,
manejar
sesiones o procesar solicitudes. Con pocas líneas de código, puedes generar un CRUD de
API Rest
completamente documentado.
Si no lo has hecho, habilitar las migraciones al encender el servidor
.
Por defecto utiliza SQLite, creara un archivo de base lila.sqlite
en la
raíz del
proyecto.
term
python -m cli.migrations
routes/api.py
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 manejar rutas de la API.
# Modelo de Pydantic para validaciones al crear o modificar un usuario.
class UserModel(BaseModel):
email: EmailStr
name: str
token: str
password: str
# Definición de middlewares para las operaciones CRUD
middlewares_user = {
"get": [],
"post": [],
"get_id": [],
"put": [],
"delete": [check_session, check_token],#Ejemplo de middleware para sesión web con 'check_session' y jwt con 'check_token'
}
# Generación del CRUD automáticamente 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 lista para
personalizar la
seguridad y validaciones en cada operación de rest_crud_generate
.
Para generar la documentación recuerda siempre
ejecutar luego de las rutas router.swagger_ui()
y
router.openapi_json()
Parámetros de la función rest_crud_generate
A continuación, se detallan los parámetros que acepta esta función para generar el CRUD
automáticamente:
core/routing.py
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='',#Retorna siempre con la primer clave 'data' para una lista o diccionario
user_id_session:bool| str=False #Ejemplo para validar en las querys con 'user id' en la clausula where 'user_id'= id session_user (tomado de la sesión)
) :
Documentación automática
A continuación, se muestra un ejemplo de la documentación generada para la función
rest_crud_generate
:
Dirigite a http://127.0.0.1:8001/docs
, o como hayas configurado tu .env (por
HOST y
PORT)
GET - Obtener todos los usuarios
GET -Sálida 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 'usuarios' , pero puedes aplicarlo como quieras según tu
lógica,
'productos','comercios',etc. Hasta modificando el core en core/routing.py
Tanto para la función por el metodo 'POST' o 'PUT',
Si el framework detecta que pasas datos en el cuerpo de la solicitud como:
'password' , los codificará automáticamente con argon2 para hacerlo seguro.
Body de ejemplo en la request a enviar :
{
"email":"example@example.com",
"name":"name",
"password":"my_password_secret",
}
Luego, si pasas 'token' o 'hash', con la función del helper
generate_token_value
, genera automáticamente un token, que se guardará en base como columna 'token'
con el valor generado por la función
.
Body example :
{
"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.
Body example :
{
"email":"example@example.com",
"name":"name",
"password":"my_password_secret",
"token":"",
"created_at":""
}
Para los metodos 'PUT','GET' (get_id) o 'DELETE'
Es opcional según la logica de cada API REST,le puedes pasar como
query string
,
user_id
o id_user
, un ejemplo seria por GET,PUT o DELETE como metodo
a la url http://127.0.0.1:8001/api/products/1?user_id=20
Donde válida que existe el ID de producto '1' pero también que pertenezca al id de
usuario '20' .
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 (plantillas,
rutas y configuración) se encuentran en la carpeta admin
, lo que facilita
su personalización
y extensión.
Características Principales
- Autenticación: Login y logout seguro para administradores.
- Gestión de Modelos: Genera automáticamente rutas y vistas para
administrar tus modelos.
- Métricas del Sistema: Monitorea el uso de memoria y CPU tanto de la
aplicación como del servidor.
- Gestión de Contraseñas: Permite a los administradores cambiar sus
contraseñas.
- Registros: Permite a los administradores ver los
Logs
de la aplicación.
Uso Básico
Para usar el panel de administración, debes importar la clase Admin
desde
admin.routes
y pasarle una lista opcional de modelos que quieras
administrar.
Cada modelo debe implementar un método get_all
para mostrarse en el panel.
main.py
# 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, debes ejecutar las migraciones:
Terminal
python -m cli.migrations
Creación de Usuarios Admin
Los usuarios administradores se pueden crear mediante línea de comandos con parámetros
personalizables:
Terminal
# Uso por defecto (contraseña aleatoria generada)
python -m cli.create_admin
# Con nombre de usuario y contraseña personalizados
python -m cli.create_admin --user miadmin --password micontraseñasegura
Rutas Generadas
El panel de administración genera automáticamente las siguientes rutas:
- Login:
/admin/login
(GET/POST)
- Logout:
/admin/logout
(GET)
- Cambiar Contraseña:
/admin/change_password
(GET/POST)
- Dashboard:
/admin
(GET)
- Gestión de Modelos:
/admin/{model_plural}
(GET)
Middleware de Autenticación
Para proteger rutas y asegurar que solo administradores autenticados puedan acceder, usa
el
decorador @admin_required
de core/admin.py
.
Uso del Middleware
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:
- Uso de memoria del framework Lila.
- Uso de CPU del framework Lila.
- Uso de memoria del sistema.
- Uso de CPU del sistema.
Estas métricas se actualizan cada 10 segundos.
Registros (Logs)
En Lila, usamos un middleware que puedes activar o desactivar si deseas utilizar
Logs
para información, advertencias o errores en tu aplicación.
El middleware se encuentra en core/middleware.py
y se añade 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.
Minificar archivos para producción
Con el comando lila-minify
puedes minificar tus archivos js,css y html, que
esten en las carpetas public
y templates
Comando de Terminal
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 de systemd para gestionar el proceso.
1. Crear un Entorno Virtual
Terminal
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
2. Crear un Script de Inicio
start_lila.sh
#!/bin/bash
source /ruta/a/tu/proyecto/venv/bin/activate
python /ruta/a/tu/proyecto/main.py
3. Crear un Servicio de systemd
/etc/systemd/system/lila.service
[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
Terminal
sudo systemctl daemon-reload
sudo systemctl enable lila.service
sudo systemctl start lila.service
Servidor Windows
En Windows, puedes usar el Programador de Tareas para ejecutar tu aplicación al inicio.
- Crea un script `.bat` o `.ps1` para iniciar tu aplicación, similar al script
`start_lila.sh`.
- Abre el Programador de Tareas.
- Crea una nueva tarea que se ejecute al inicio del sistema.
- Establece la acción para que ejecute tu script.
Desactivar el Modo de Depuración
En tu `main.py`, asegúrate de desactivar el modo de depuración para producción.
main.py
app = App(debug=False, routes=all_routes)