Installation

Installation
$ python -m venv venv # Or python3 -m venv venv
$ source venv/bin/activate # On Windows use `venv\Scripts\activate`
$ pip install lila-framework
$ lila-init
$ python main.py #Or python3 main.py

Purpose: Configure and start the application. main.py is the main entry point where the application is configured and initialized.
main.py Explanation

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

Routes

Routes are the access points to the application. In Lila, routes are defined in the routes directory (by default, but you can place them wherever you want) and imported into main.py for use. Routes can be configured to handle HTTP requests, API methods, and more.

In addition to JSONResponse you can use HTMLResponse, RedirectResponse and PlainTextResponse, or StreamingResponse , to transmit data in real-time (useful for streaming video/audio or large responses).

Below is an example of how routes are defined in Lila:

routes/api.py
                            
#Import from lila.core JSONResponse .
from lila.core.responses import JSONResponse 

# Manages routes for API endpoints.
from lila.core.routing import Router  
# Initializes the router instance to handle API app.routes.
router = Router()


# Defines a simple API route that supports the GET method.
@router.route(path='/api', methods=['GET'])
async def api(request: Request):
"""Api function"""
# English: Returns a simple JSON response for API verification.
return JSONResponse({'api': True})   
                            
                        

Get url parameteres

In this function, we receive a parameter via the URL using {param}. If the parameter is not provided, it defaults to 'default'. The response is a JSON containing the received value.

Receiving GET Parameters
        
@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})
        
        

Or you canl also do it like this

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})
                            
        
        

Basic Imports
        
from lila.core.responses import JSONResponse  # Simplifies sending JSON responses.
from lila.core.routing import Router  # Manages API app.routes.
from lila.core.request import Request  # Handles HTTP requests in the application.
from pydantic import EmailStr, BaseModel  # Validates and parses data models for input validation.
from app.helpers.helpers import get_user_by_id_and_token
from app.middlewares.middlewares import validate_token

router = Router()
        
        
Middleware and Decorators Usage

Middlewares allow intercepting requests before they reach the main logic of the API. In this example, we use @validate_token to validate a JWT token in the request header.

Middleware Validation
        
@router.route(path='/api/token', methods=['GET','POST'])
@validate_token  # Middleware to validate JWT token.
async def api_token(request: Request):
    return JSONResponse({'api': True})
        
        
Data Validation with Pydantic

Pydantic allows defining data models that automatically validate user input. Additionally, specifying a model in the route generates automatic documentation in /docs.

Validation with Pydantic
        

from pydantic import EmailStr, BaseModel 
class ExampleModel(BaseModel):
    email: EmailStr  # Ensures a valid email.
    password: str  # String for password.

@router.route(path='/api/example', methods=['POST'], model=ExampleModel)
async def login(request: Request):
    body = await request.json()
    try:
        input = ExampleModel(**body)  # Automatic validation with 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})
        
        
Automatic Documentation Generation

Thanks to the integration with Pydantic, the API documentation is generated automatically and is accessible from /docs. You can also generate an OpenAPI JSON file for external tools.

Documentation Generation
        
router.swagger_ui()  # Enables Swagger UI for API documentation.
router.openapi_json()  # Generates OpenAPI JSON for external tools.
        
        
Importing Routes in main.py

To use the routes defined in the router, you need to obtain them with router.get_routes() and import them into main.py.

Route Import
        
routes = router.get_routes()  # Retrieves all defined app.routes.
        
        

Static files

To load static files(js,css,etc) you can use the mount() method.

What will be received as parameters that trade by default path: str = '/public', directory: str = 'static', name: str = 'static'

Static

from lila.core.routing import Router
# Creating an instance of the Router to define the routes, if it was not previously created in the file
router = Router()
# Mounting the static files in the 'static' folder, url ='/public' by default
router.mount()
 

Templates Render (Jinja2 + Lila JS)

In Lila, you can use Jinja2 to render HTML with server-side data or Lila JS to create reactive client-side components, or even combine both approaches according to your needs.

Rendering Options

Lila offers three approaches to build your application:

1. Traditional Jinja2

Full server-side rendering using Jinja2 templates with context data.


<p>{{ translate['Welcome'] }} {{ user.name }}</p>
                                                

Advantages: Perfect SEO, fast initial load, simple.

2. Lila JS (SPA)

Fully reactive single-page application on the client side.


<span data-bind="message"></span>
<input data-model="username">
                                                

Advantages: Fluid user experience, no page reloads.

3. Hybrid Approach

Combine Jinja2 for base structure and Lila JS for interactive parts.


<div data-component="InteractivePart"></div>
<p>{{ static_content }}</p>
                                                

Advantages: The best of both worlds.

Render with Jinja2

In Lila, Jinja2 is used by default to render HTML templates and send them to the client. With context, you can pass information such as translations, data, values, lists, dictionaries or whatever you need.

The parameters that the render function from lila.core.templates can receive are:


request: Request, 
template: str, 
context: dict = {}, 
theme_: bool = True,
translate: bool = True, 
files_translate: list = []
                                        

Lila JS: Reactive System

Lila JS is a minimalist library to create reactive interfaces with vanilla JavaScript.

Documentation Lila JS

Key Concepts

Reactive State

State is a special object that automatically updates the UI when it changes.


state: () => ({
    email: '',
    password: ''
})
                                                

Two-Way Data Binding

Bidirectional binding between inputs and state using data-model.


<input data-model="email">
<span data-bind="email"></span>
                                                

Components

Reusable units with their own state, template and actions.


App.createComponent('Login', {
    template: 'login-template',
    state: () => ({...}),
    actions: {...}
});
                                                

Routing

Client-side navigation between components.


App.addRoute('/login', 'Login');
handleRouting();
                                                

Complete Example: Authentication System

This example shows how to combine Jinja2 for the base structure and Lila JS for interactivity.

auth.html - Base Structure

templates/html/auth.html

<!DOCTYPE html>
<html lang="{{lang}}">
<head>
    <meta charset="UTF-8">
    <title>{{title}}</title>
    <link rel="stylesheet" href="/public/css/styles.css" />
    <script src="/public/js/lila.js"></script>
</head>
<body>
    <main id="app-lila" class="container"></main>
    {% include 'auth/login.html' with context %}
    {% include 'auth/register.html' with context %}
    {% include 'auth/footer.html' with context %}
    <script>
        handleRouting();
    </script>
</body>
</html>
                                        

Explanation: This file serves as the base layout using Jinja2. It includes:

  • Translations from context with {{lang}} and {{title}}
  • A main container app-lila where components will be mounted
  • Inclusion of partial templates for login, register and footer
  • Router initialization with handleRouting()

login.html - Reactive Component

templates/html/auth/login.html

<template data-template="login-template">
    <div class="flex center">
        <article class="shadow rounded mx-m">
            <h1 class="flex center">{{translate['login']}}</h1>
            <form data-action="loginFetch">
                <div class="input-icon">
                    <i class="icon-email"></i>
                    <input type="email" data-model="email" placeholder="Email" required />
                </div>
                <div class="input-icon">
                    <i class="icon-lock"></i>
                    <input type="password" data-model="password" 
                            placeholder="{{translate['password']}}" required />
                </div>
                <button type="submit">{{translate['login']}}</button>
            </form>
            <a href="/register" data-link>{{translate['register']}}</a>
        </article>
    </div>
    <div data-component="Footer"></div>
</template>

<script>
    App.createComponent('Login', {
        template: 'login-template',
        state: () => ({
            email: '',
            password: ''
        }),
        actions: {
            loginFetch: async ({ state, event }) => {
                event.preventDefault();
                const data = { email: state.email, password: state.password };
                alert('Login: ' + JSON.stringify(data));
            }
        }
    });
    
    App.addRoute('/login', 'Login');
    App.addRoute('*', 'Login'); // Default route
</script>
                                        

Key Features:

  • Two-way data binding: Inputs with data-model automatically sync with state.
  • Actions: data-action binds the form to the loginFetch method.
  • Nested component: Footer is included with data-component="Footer".
  • Routing: Links with data-link navigate without page reload.
  • Combination with Jinja2: Use of {{translate}} for internationalized texts.

register.html - Component with Validation

templates/html/auth/register.html

<template data-template="register-template">
    <div class="flex center container">
        <article class="shadow rounded mx-m">
            <h1>{{translate['register']}}</h1>
            <form data-action="registerFetch">
                <input type="text" data-model="name" placeholder="{{translate['name']}}">
                <input type="email" data-model="email" placeholder="Email">
                <input type="password" data-model="password" placeholder="{{translate['password']}}">
                <input type="password" data-model="password_2" placeholder="{{translate['confirm_password']}}">
                <button type="submit">{{translate['register']}}</button>
            </form>
            <div>
                <p>Current values:</p>
                <p>Name: <span data-bind="name"></span></p>
                <p>Email: <span data-bind="email"></span></p>
            </div>
            <a href="/login" data-link>{{translate['login']}}</a>
        </article>
    </div>
</template>

<script>
    App.createComponent('Register', {
        template: 'register-template',
        state: () => ({
            name: '',
            email: '',
            password: '',
            password_2: ''
        }),
        actions: {
            registerFetch: async ({ state, event }) => {
                event.preventDefault();
                if (state.password !== state.password_2) {
                    alert("{{translate['passwords_dont_match']}}");
                    return;
                }
                const data = { 
                    name: state.name, 
                    email: state.email, 
                    password: state.password 
                };
                // Send data to server...
            }
        }
    });
    
    App.addRoute('/register', 'Register');
</script>
                                        

Key Points:

  • Shows real-time state values with data-bind
  • Basic validation directly in the action
  • Combined use of reactive data and static translations

footer.html - Simple Component

templates/html/auth/footer.html

<template data-template="footer-template">
    <footer class="mt-4 container">
        <div class="flex between">
            <a href="/set-language/es">Español</a>
            <a href="/set-language/en">English</a>
        </div>
    </footer>
</template>

<script>
    App.createComponent('Footer', {
        template: 'footer-template'
    }); 
</script>
                                        

Notes:

  • Stateless component
  • Reusable in any part of the application
  • Doesn't require complex JavaScript logic

Advantages of This Approach

🚀 Performance

Combines Jinja2's initial speed with SPA fluidity.

🔍 Optimized SEO

Critical content is server-rendered for search engine bots.

💡 Progressive

Start with Jinja2 and add interactivity where needed.

🛠️ Simple

No complex configuration or heavy dependencies.

Lila JS Quick Reference

Attribute/Function Description Example
data-component Mounts a component inside another <div data-component="Footer"></div>
data-bind Displays a state property value <span data-bind="email"></span>
data-model Two-way binding for inputs <input data-model="username">
data-action Binds an action to an event <button data-action="submit">
data-link Navigation between routes without reload <a href="/about" data-link>
App.createComponent Creates a new component App.createComponent('Login', {...});
App.addRoute Defines a route for a component App.addRoute('/login', 'Login');
handleRouting() Initializes the routing system <script>handleRouting();</script>

Lila JS Documentation

Lila JS is a minimalist JavaScript micro-framework for building modern web applications. It provides a reactive system, routing, and components in a lightweight package.

Core Concepts

Components

Components are the building blocks of a Lila JS application. They encapsulate a template, state, and actions.

Component Example

App.createComponent('Home', {
    template: 'home-template',
    state: () => ({
        message: 'Welcome to Lila JS!'
    }),
    actions: {
        updateMessage: ({ state }) => {
            state.message = 'The message has been updated!';
        }
    }
});
                                    

Routing

Lila JS includes a simple router to map URLs to components.

Routing Example

App.addRoute('/', 'Home');
App.addRoute('/about', 'About');
                                    

Initialization

To start the application and handle routing, you need to call `handleRouting()`.

Initialization Example

handleRouting();
                                    

Session Management

Lila provides a simple yet powerful session management system built on top of itsdangerous for cryptographically signed session data. This ensures that session data cannot be tampered with on the client side.

The Session class provides a set of static methods to handle session data.

Setting Session Data

The setSession method is used to set session data in a cookie. It can handle strings, dictionaries, and lists, which are automatically serialized to JSON if needed.

setSession example

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": "Session has been set"})
    session_data = {"user_id": 123, "username": "lila_user"}
    Session.setSession(new_val=session_data, response=response, name_cookie="user_session")
    return response
                                    

The setSession method accepts several parameters to customize the cookie, such as secure, samesite, max_age, etc.

Getting Session Data

The getSession method retrieves the raw, signed session data from a cookie. To get the unsigned and validated session data, you should use getSessionValue or unsign.

Unsigning and Validating Session Data

The unsign and getSessionValue methods are used to get the session data in its original form after verifying the signature. getSessionValue is recommended as it also handles expired signatures gracefully.

getSessionValue example

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 session found or session is invalid/expired."}, status_code=401)
                                    

Deleting a Session

The deleteSession method is used to delete a session cookie from the client's browser.

deleteSession example

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": "Logged out successfully"})
    Session.deleteSession(response=response, name_cookie="user_session")
    return response
                                    

Full Session Class Reference

Here is the full code for the Session class for your reference.

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
                                    

API Reference & Examples

Login Form Example

This example demonstrates how to create a simple login form with validation.

Login Component

<template data-template="login-template">
    <form data-action="login">
        <input type="email" data-model="email" placeholder="Email" required>
        <input type="password" data-model="password" placeholder="Password" required>
        <button type="submit">Login</button>
    </form>
</template>

<script>
    App.createComponent('Login', {
        template: 'login-template',
        state: () => ({
            email: '',
            password: ''
        }),
        actions: {
            login: ({ state, event }) => {
                event.preventDefault();
                if (state.email && state.password) {
                    // Perform login logic here
                    console.log('Logging in with:', state.email, state.password);
                } else {
                    alert('Please fill in all fields.');
                }
            }
        }
    });

    App.addRoute('/login', 'Login');
</script>
                                    

File Upload Helper

The upload helper provides a complete solution for handling file uploads in your application, including validation, security checks, and translation support.

This helper automatically handles:

  • HTTP method validation (POST only)
  • Content-Type validation (multipart/form-data)
  • File extension validation
  • File size limits
  • Empty file detection
  • Secure filename handling
  • Automatic directory creation
  • Translation support for error messages

Parameters

The upload function accepts these parameters:

Parameters

request: Request          # Starlette request object
name_file: str | list     # Field name for file upload (default: 'file')
UPLOAD_DIR: str           # Directory to save files (default: 'uploads')
ALLOWED_EXTENSIONS: set   # Allowed file extensions (default: {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'})
MAX_FILE_SIZE: int        # Maximum file size in bytes (default: 10MB)
            

Usage Example

Route Implementation

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
                                    

Frontend Implementation

HTML + JavaScript

<form onsubmit="upload(event);">
    <fieldset>
        <input type="file" name="file" required>
    </fieldset>
    <button type="submit">
        <i class="icon-check-circle"></i>
        Upload Files
    </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('File uploaded successfully!');
        } catch (error) {
            alert(error.message);
        }
    }
</script>
                                    

Response Format

The helper returns a JSONResponse with this structure:

Success Response

{
    "file": "/uploads/filename.ext",
    "success": true,
    "message": "File uploaded successfully"
}
                                    
Error Response

{
    "error": true,
    "success": false,
    "message": "Error message in user's language"
}
                                    

Markdown /HTML

To render markdown files, the renderMarkdown function is used, which receives the following parameters: request,file : str , base_path:str ='templates/markdown/',css_files : list = [],js_files:list=[],picocss : bool =False

file is the name of the file, starting from the base_path of the directory located in templates/markdown/, for example: index.md. It will look for an index.md file in the markdown directory inside the templates folder (templates/markdown/index.md).

css_files and js_files are lists of CSS and JS files that will be loaded in the generated HTML file.

picocss is a boolean indicating whether the PicoCSS CSS file should be loaded.

Below is an example of how markdown files are rendered in Lilac:

Markdown
                     
from lila.core.templates import renderMarkdown

@router.route(path='/markdown', methods=['GET'])
async def home(request: Request):
    #Define a list of CSS files to include in the response
    css = ["/public/css/styles.css"]
    #Renders a markdown file with PicoCSS styling
    response = renderMarkdown(request=request, file='example', css_files=css, picocss=True)
    return response

                     
                     

Internationalization (Translations)

Translations are used to internationalize an application and display content in different languages. In Lila, translations are stored in the app/locales directory and can be dynamically loaded into the application.

Forcing Default Language in Routes

The render method accepts a lang_default parameter to force a specific language for the rendered template:

app.routes.py

# Force Spanish language
@router.route(path="/es", methods=["GET"])
async def home(request: Request):
    response = render(
        request=request,
        template="index",
        lang_default="es"  # Forces Spanish translations
    )
    return response

# Force English language
@router.route(path="/en", methods=["GET"])
async def home(request: Request):
    response = render(
        request=request,
        template="index",
        lang_default="en"  # Forces English translations
    )
    return response
                                    

Translation Files

To load a locale file, use the translate function from app.helpers.helpers. You can access translations using:

  • translate - returns a dictionary with all translations
  • translate_ - returns a specific translation (returns original text if not found)

Example translation file (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"
    }
}
                                    

Using Translations

Get a specific translation:

example.py

from app.helpers.helpers import translate_

msg_error_login = translate_(
    key="Incorrect email or password", 
    request=request, 
    file_name="guest"
)
                                    

Get all translations from a file:

example.py

from app.helpers.helpers import translate

all_translations = translate(
    request=request, 
    file_name="guest"
)
                                    

Models (SQLAlchemy)

Models are used to define the structure of data in the application.

SQLAlchemy as the default ORM for database management. SQLAlchemy allows creating database models, executing queries, and handling migrations efficiently.

Using Models

The `Base` class, imported from `lila.core.database`, serves as the foundation for all models . Models inherit from `Base` to define database tables with SQLAlchemy.

Example: User Model

This example demonstrates how to create a `User` model using SQLAlchemy. The model defines a `users` table with columns such as `id`, `name`, `email`, `password`, `token`, `active`, and `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)

#Example of how to use SQLAlchemy to make queries to the database
    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)#Return rows 
        return result 

#Example of how to use SQLAlchemy to make queries to the database
    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)#Retrurn row
        return row
    
    #Example using ORM abstraction in 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
    
    
#Example of how to use the class to make queries to the database
users = User.get_all()
user = User.get_by_id(1)
                    
                    

For details on SQLAlchemy, visit the official documentation: SQLAlchemy Documentation.

Middlewares

Middleware functions are used to intercept requests before they reach the main logic of the application. In Lila, middlewares are defined in the app/middlewares directory (can be modified to any file and/or directory). Middlewares can be used for tasks such as authentication, logging, and error handling.

By default, Lila comes with 3 middlewares to start any application. Middlewares can be used with decorators @my_middleware.

login_required, to validate that you have a signed session, for the 'auth' key that is passed as a parameter to be able to modify it as you wish. If this session is not found, it redirects to the URL that is passed as a parameter, by default, it is "/login". Otherwise, it will continue its course executing the route or function.

Then we have session_active, which is used to verify if you have an active session. It will redirect to the URL that is received as a parameter, by default, it is "/dashboard".

The third is validate_token, which is used to validate a JWT token thanks to the get_token helpers imported in 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': 'Invalid token'},status_code=401)
    
    token = get_token(token=token)
    if isinstance(token,JSONResponse):
        return token 
                
            
            

Here we give you several examples of how to use all 3, with the decorators.
Middlewares in routes
                        
#Middleware to validate the JWT Token. 
@router.route(path='/api/token', methods=['GET','POST'])
@validate_token #Middleware
async def api_token(request: Request):
    """Api Token function"""
    print(get_user_by_id_and_token(request=request))
    return JSONResponse({'api': True}) 

#Middleware to validate session active
@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 validate if user get session active (if user get session redirect '/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 


                        
                    

Security Middleware

Lila Framework includes a built-in ErrorHandlerMiddleware that not only handles unhandled exceptions but also provides robust security checks to protect your application from malicious requests. This middleware is designed to block suspicious IPs, URLs, and sensitive paths, ensuring that your application remains secure.

Features

  • IP Blocking: Blocks IP addresses that have been flagged for malicious activity. IPs are blocked for 6 hours by default.
  • URL Blocking: Blocks specific URLs that are known to be malicious or suspicious.
  • Sensitive Paths Blocking: Blocks access to sensitive paths such as /etc/passwd, .env, and others.
  • Malicious Extensions Blocking: Blocks URLs containing malicious file extensions like .php, .asp, .jsp, and .aspx.
  • HTTP Detection: Blocks requests containing "http" in query parameters or body content.

Configuration

The middleware uses three JSON files located in the app/security directory:

  • blocked_ips.json: Stores blocked IPs with their expiration time.
  • blocked_urls.json: Stores blocked URLs with their expiration time.
  • sensitive_paths.json: Stores a list of sensitive paths to block.

If these files do not exist, they are automatically created and initialized with default values:

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"
]
                                    

Usage

The ErrorHandlerMiddleware is automatically applied to all requests. You can customize its behavior by modifying the JSON files in the security directory.

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 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 )

Connections to database

To use connections, you need to import the Database class from lila.core.database. With that you can connect to your database, which can be SQLite, MySLQ, PostgreSQL or whatever you want to configure.

Below we leave you the example of how to connect. The connection will close automatically after being used, so you can use it as in this example in the variable connection

app/connections.py

            
from lila.core.database import Database

#SQLite

#Example connection to a sqlite database 
config = {"type":"sqlite","database":"test"} #test.db
connection = Database(config=config)
connection.connect()

#MySql

#Example connection to a mysql database 
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
                

                
                

Migrations

In Lila Framework, database migrations can be managed using SQLAlchemy and Lila's configuration to make migrations as easy as possible. The framework now supports command-line migrations through Typer, providing a more intuitive and flexible way to manage your database schema.


Migration Methods

There are two main ways to define database tables:

1. Using Table

This method manually defines the table structure using SQLAlchemy's Table object.

cli/migrations.py

from sqlalchemy import Table, Column, Integer, String, TIMESTAMP
from app.connections import connection
import typer
import asyncio

app = typer.Typer()

# Example of creating migrations for '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])  # for tables
        connection.migrate()
        print("Migrations completed")
        return True
    except RuntimeError as e:
        print(e)
        return False

@app.command()
def migrate(refresh: bool = False):
    """Run database migrations"""
    success = asyncio.run(migrate_async(connection, refresh))
    if not success:
        raise typer.Exit(code=1)

if __name__ == "__main__":
    app()
                                    

2. Using Models (Recommended)

This approach defines database tables as Python classes that inherit from "Base". This is the recommended method as it provides more structure and ORM capabilities.

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 (Models version)

from app.connections import connection
from lila.core.database import Base  # Import Base for migrations in models
from app.models.user import User  # Import models for migrations
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)  # for models
        print("Migrations completed")
        return True
    except RuntimeError as e:
        print(e)
        return False

@app.command()
def migrate(refresh: bool = False):
    """Run database migrations"""
    success = asyncio.run(migrate_async(connection, refresh))
    if not success:
        raise typer.Exit(code=1)

if __name__ == "__main__":
    app()
                                    

Running Migrations

To execute migrations, use the following command in your terminal:

Terminal Command

# Basic migration
python -m cli.migrations 

# Refresh all tables (drop and recreate)
python -m cli.migrations --refresh
                                    

Command Options

  • migrate: Run the database migrations
  • --refresh: Optional flag to drop and recreate all tables

Note: When using Models, make sure to import all your model classes in the migrations file so SQLAlchemy can detect them for migrations.

React Integration with Lila

Lila provides a seamless integration with React, allowing you to develop your frontend with a modified Vite + React setup while keeping strong SEO capabilities, caching, and HTML hydration. This setup ensures your React app can be prerendered and recognized by Google Bots and AI.

Starting a React Project in Lila

To create or run a React project inside Lila, execute the following command:

CLI Command

# For Python 3 environments
python3 -m cli.react [--name PROJECT_NAME]

# Or if your default Python is python
python -m cli.react [--name PROJECT_NAME]
            

Parameters:
--name: Optional project name, default is "react".

Development CORS Configuration

When running React in development mode, Lila automatically edits main.py to add the necessary CORS configuration. By default:

main.py CORS snippet

# English: for development react
# Español: para desarrollo react
cors = {
    "origin": ["http://localhost:5173"],
    "allow_credentials": True,
    "allow_methods": ["*"],
    "allow_headers": ["*"]
}
            
Automatic Routes and Asset Mounting

Lila modifies app/routes/routes.py automatically for your React build. This ensures the React build assets are served correctly and the index.html is rendered from the cache.

routes.py Example

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
            

Remember to modify the main.py file when passing your routes to initialize the application, always pass api_routes first, or whatever you have named it.
This is key to getting your API routes without colliding with the general React route. @router.route(path="/{path:path}", methods=["GET"]) , where React, with React Router, or whatever you use, can handle all the routes.
Remaining this way in main.py

main.py

all_routes = list(itertools.chain( api_routes,routes))
#First api_routes, then routes (where the general React route is) 


Prerendering and Caching

To improve SEO, Lila supports prerendering your React pages and caching them. After building your React app with npm run build, you can generate the cached HTML:

Generate Cache

python -m cli.prerender --url http://localhost:8001
# or python3 -m cli.prerender
            

Then, in your react/index.html dev file, include the cached HTML for SEO hydration:

index.html snippet

{% include "react/cache/index.html" ignore missing %}
Startup Integration in main.py

You can optionally execute the prerender automatically on server startup by uncommenting the following section in main.py:

main.py prerender on startup

# 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()
#     ])
            
SEO and Hydration

By including the cached HTML in the root div, React will rehydrate the content on the client side. This gives you:

  • Strong SEO recognition by Google Bots and AI.
  • Faster first paint with pre-rendered HTML.
  • Optional daily caching using a scheduled prerender task.
React Helpers and Context

You can pass context from Jinja2 templates to React using <script> tags in templates/html/react/index.html. Useful for translations, configuration, or any dynamic data.

Automatic API Documentation

Lila continues to generate API documentation automatically using Pydantic models. All helpers and middlewares can be combined seamlessly with React routes.

Session Validation with React

In the backend, you can add a middleware to validate the session. You can also use JWT, but Session is more secure because it is signed by itsdangerous.

This way, you can validate requests and store public IDs, tokens, etc., to validate sessions, or you can use JWT or both. The main recommendation is to validate the session this way or with login_required, which is another middleware that can also be configured there. This way, Lila and React are connected for the developer. In production, thanks to the if DEBUG condition, it will be secure with the signature of itsdangerous in the Session class.

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

# Define a simple API route that supports GET method.
@router.route(path="/api", methods=["GET"])
async def api(request: Request):
    """Api function"""
    # Returns a simple JSON response for API verification.
    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


# Define an API route that supports GET and POST methods.
@router.route(path="/api/token", methods=["GET", "POST"])
# Middleware to validate the JWT Token.
@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)
                                    
React Component

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://127.0.0.1:8000/api/token', { credentials: 'include' });
      const json = await resp_.json();

      console.log(json);
    };
    start();
  }, []);

  return (
    <div>
      {/* Your component JSX */}
    </div>
  );
}
                                    

Simple Generation of REST API CRUD

At Lila, we have a simple way to generate CRUDs with automatic documentation, allowing you to create your REST API efficiently.

Thanks to the combination of SQLAlchemy and Pydantic models, it is possible to perform data validations and execute structured queries for API generation.

Additionally, you can integrate custom middlewares to validate tokens, manage sessions, or process requests. With just a few lines of code, you can generate a fully documented REST API CRUD.


If you haven't already, enable migrations when you start the server . By default it uses SQLite, it will create a database file lila.sqlite in the project root.

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  # Database connection with SQLAlchemy
from app.models.user import User  # SQLAlchemy 'User' model

router = Router()# Initialize the router instance for managing API app.routes.


# Pydantic model for validations when creating or modifying a user.
class UserModel(BaseModel):
    email: EmailStr
    name: str
    token: str
    password: str

# Middleware definitions for CRUD operations
middlewares_user = {
    "get": [],
    "post": [], 
    "get_id": [],
    "put": [],
    "delete": [check_session, check_token],#Example middlewares for web session 'check_session' and jwt 'check_token'
}

# Automatically generate CRUD with validations and configurations
router.rest_crud_generate(
    connection=connection,  # Database connection
    model_sql=User,  # SQLAlchemy model
    model_pydantic=UserModel,  # Pydantic model
    select=["name", "email", "id", "created_at", "active"],  # Fields to select in queries
    delete_logic=True,  # Enables logical deletion (updates 'active = 0' instead of deleting records)
    active=True,  # Automatically filters active records ('active = 1')
    middlewares=middlewares_user,  # Custom middlewares for each CRUD action
)
                            

You can create your own middlewares and pass them as a list to customize security and validations for each rest_crud_generate operation.

To generate the documentation, always remember to run it after the routes router.swagger_ui() and router.openapi_json()

Function Parameters for rest_crud_generate

Below are the parameters that this function accepts to generate CRUD automatically:

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='',#Return with prefix list o dict 'data' first key
    
    user_id_session:bool| str=False #Example 'user id' to validate in query with where 'user_id'= id session_user
 
) :                    
Automatic Documentation

Below is an example of the generated documentation for the rest_crud_generate function:

Go to http://127.0.0.1:8001/docs, or as configured in your .env file (by HOST and PORT).

GET - Retrieve all users
GET - Retrieve all users
GET - Output of all users
GET - Output
POST - Create new user
POST - Create new user
GET_ID - Retrieve a specific user
GET_ID - Retrieve a specific user
PUT - Update user
PUT - Update user
DELETE - Delete user
DELETE - Delete user

In this example, we did it with 'users', but you can apply it however you want according to your logic ,'products','stores',etc. Even modifying the core in core/routing.py.


For both the 'POST' or 'PUT' method function, If the framework detects that you pass data in the request body such as: 'password' , it will automatically encode it with argon2 to make it secure. Body example :


{
"email":"example@example.com",
"name":"name",
"password":"my_password_secret"
}
                        
                        

Then, if you pass 'token' or 'hash', with the helper function generate_token_value , it automatically generates a token, which will be saved in the database as the 'token' column with the value generated by the function . Body example :


{
"email":"example@example.com",
"name":"name",
"password":"my_password_secret",
"token":""
}
                        
                        

With 'created_at' or 'created_date' , it will save the date and time of the moment, as long as that field exists in the database table. Body example :


{
"email":"example@example.com",
"name":"name",
"password":"my_password_secret",
"token":"",
"created_at":""
}
                        
                        

For the 'PUT', 'GET' (get_id) or 'DELETE' methods It is optional according to the logic of each REST API, you can pass it as query string , user_id or id_user, an example would be GET, PUT or DELETE as a method to the url http://127.0.0.1:8001/api/products/1?user_id=20

Where it validates that the product ID '1' exists but also that it belongs to the user ID '20' .

Admin Panel

The Admin module allows you to manage an admin panel for your application. It includes authentication, model management, system metrics, and more. This panel is highly customizable and integrates easily with your application.

The admin panel is now more modular and flexible. All admin-related components (templates, routes, and configuration) are located in the admin folder, making it easier to customize and extend.

Key Features

  • Authentication: Secure login and logout for administrators.
  • Model Management: Automatically generates routes and views to manage your models.
  • System Metrics: Monitors memory and CPU usage for both the application and the server.
  • Password Management: Allows administrators to change their passwords.
  • Logs: Enables administrators to view application Logs.

Basic Usage

To use the admin panel, you need to import the Admin class from admin.routes and pass it an optional list of models you want to manage. Each model must implement a get_all method to be displayed in the admin 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))
                
            

Setup and Migrations

Before using the admin panel, you need to run the migrations:

Terminal
                
python -m cli.migrations
                
            

Creating Admin Users

Admin users can be created via command line with customizable parameters:

Terminal
                
# Default usage (random password generated)
python -m cli.create_admin

# With custom username and password
python -m cli.create_admin --user myadmin --password mysecurepassword
                
            

Generated Routes

The admin panel automatically generates the following routes:

You can access the templates in the templates/html/admin folder.

  • Login: /admin/login (GET/POST)
  • Logout: /admin/logout (GET)
  • Dashboard: /admin (GET)
  • Model Management: /admin/{model_plural} (GET)

Authentication Middleware

To protect routes and ensure only authenticated administrators can access them, use the @admin_required decorator from core/admin.py.

Middleware Usage
                
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)
                
            

System Metrics

The admin panel displays real-time metrics, including:

  • Lila framework memory usage.
  • Lila framework CPU usage.
  • System memory usage.
  • System CPU usage.

These metrics are updated every 10 seconds.

Logs

In Lila, we use a middleware that you can enable or disable if you want to use Logs for info, warnings, or errors in your application.

The middleware is located in core/middleware.py and is added to the application with: app.add_middleware(ErrorHandlerMiddleware).

This helps generate logs that you can view in the admin panel, organized by date (in folders) and type.

admin


admin

Minify files for production

With the lila-minify command you can minify your js, css and html files, which are in the public and templates folders.

Terminal Command

lila-minify 
#Or
python -m cli.minify

Deployment to Production

Linux Server (with systemd)

To deploy your Lila application on a Linux server, it's recommended to use a virtual environment and a systemd service to manage the process.

1. Create a Virtual Environment

Terminal

python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
                                    

2. Create a Start Script

start_lila.sh

#!/bin/bash
source /path/to/your/project/venv/bin/activate
python /path/to/your/project/main.py
                                    

3. Create a systemd Service

/etc/systemd/system/lila.service

[Unit]
Description=Lila Framework Service
After=network.target

[Service]
User=your_user
Group=your_group
WorkingDirectory=/path/to/your/project
ExecStart=/path/to/your/project/start_lila.sh
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target
                                    

4. Enable and Start the Service

Terminal

sudo systemctl daemon-reload
sudo systemctl enable lila.service
sudo systemctl start lila.service
                                    

Windows Server

On Windows, you can use the Task Scheduler to run your application at startup.

  1. Create a `.bat` or `.ps1` script to start your application, similar to the `start_lila.sh` script.
  2. Open Task Scheduler.
  3. Create a new task that runs at system startup.
  4. Set the action to run your script.

Disable Debug Mode

In your `main.py`, make sure to turn off debug mode for production.

main.py

app = App(debug=False, routes=all_routes)