Skip to main content

Lila Framework

Lila Framework v2.0.0

Lightweight full-stack Python web & desktop framework
Starlette + Pydantic v2 + SQLAlchemy 2.0 Async + Jinja2 + a native View Transitions SPA.

PyPI Downloads Documentation License


⚡ What is Lila?

Lila is a small, batteries-included Python web framework for people who want the speed of Starlette, the validation of Pydantic v2 and an SPA experience without a Node.js build step. It is designed for 1–2 worker deployments on a cheap VPS: SQLite WAL by default, an in-process LRU cache, optional Redis when it is actually reachable, and a file cache fallback.

  • 🪶 Light on import: import lila is lazy (~19 MB RSS). A full App is around 42 MB on Python 3.14, and no SQLAlchemy is loaded unless you use models.
  • 🚀 SPA without Node.js: lila-client.js uses the native View Transitions API, hover prefetching, persistent islands (data-persist="true") and Tailwind UI components (toast, snackbar, command palette, drawer, tabs, carousel, DataTable).
  • 🧩 Templates without boilerplate: a page is just its HTML. render() adds the layout, <title>, description, Open Graph, JSON-LD, icon, styles and scripts; {{ datatable(...) }} and {{ carousel(...) }} build components from Jinja, and data-lila-form / data-action send forms and buttons without page scripts.
  • 💾 Zero infrastructure by default: Async SQLite with WAL, an in-memory LRU cache, and CACHE_DRIVER="file" for a persistent fallback. CACHE_DRIVER="redis" is used only when the Redis package is installed and the server answers; otherwise Lila logs once and falls back to memory.
  • 🗄️ Real migrations: lila migrate creates tables, adds and renames columns and creates indexes; --plan shows the diff first and risky changes are only reported.
  • ⏱️ Background work without Redis: @task queue and @every/@cron jobs on their own SQLite files, Jinja email templates, Server-Sent Events.
  • 🔐 Roles & permissions, API keys and rotating JWT refresh tokens built in.
  • 📈 Lean in production: lila css build (compiled Tailwind, no Node), precompressed static files, ETag/304, cache memory budget, Granian support, lila bench, protected /health and Prometheus /metrics.
  • 🤖 AI helper (core/ia.py): OpenAI, DeepSeek and Gemini with explicit PII masking/restoration, prompt cache and per-IP rate limits.
  • 🔐 Auth 2.0: cookie sessions without sensitive fields, Argon2id, CSRF for classic HTML forms, Google/GitHub/Discord OAuth, login lockout and email reset links (SMTP optional).
  • 🛠️ Admin 2.0: CRUD, CSV export, cache purge, logs and honest telemetry.
  • 🖥️ Desktop mode: lila desktop dev / lila desktop build (PyInstaller bundles main and the Lila templates/assets).
  • 🌍 Locales: en, es, pt and zh built in, plus project overrides in app/locales/*.json.

Runtime requirement: Python 3.10+ (Starlette 1.x and Uvicorn 0.54 require it).


📦 Installation

pip install lila-framework

# Optional:
pip install "lila-framework[redis]"       # Redis cache/worker
pip install "lila-framework[images]"      # WebP uploads
pip install "lila-framework[markdown]"    # render_markdown
pip install "lila-framework[metrics]"     # truthful admin telemetry
pip install "lila-framework[optimize]"    # rjsmin/rcssmin + Pillow
pip install "lila-framework[desktop]"     # pywebview + PyInstaller

🚀 Quick Start

lila init        # scaffold main.py, app/, public/, resources/ and .env
lila dev         # http://127.0.0.1:8000 with reload + dev SSE
lila doctor      # check .env, SECRET_KEY, database, templates, public/
lila routes      # list registered routes of main:app
# main.py
from app.config import DEBUG, HOST, PORT, WORKERS
from app.routes.web.index import router as web_router
from lila.core.app import App

app = App(
    routes=[web_router],
    debug=DEBUG,
    security=True,
    rate_limit=300,
    gzip=True,
    openapi=True,
    debug_html=DEBUG,   # mount /debug in debug mode
)

Routes with typed params

from lila.core.routing import Router
from lila.core.request import Request
from pydantic import BaseModel, EmailStr

router = Router(prefix="/api")

class UserCreate(BaseModel):
    email: EmailStr
    name: str

@router.get("/users")
async def users(request: Request, page: int = 1, q: str | None = None):
    # page is a real int; invalid values return HTTP 422
    return {"page": page, "q": q}

@router.get("/users/{id:int}", cache=60, cache_public=True)
async def get_user(request: Request, id: int):
    return {"id": id}

@router.post("/users", model=UserCreate, csrf=True)
async def create_user(request: Request, data: UserCreate):
    return {"success": True, "email": data.email}

Pages: just HTML, the layout is automatic

from lila.core.templates import render

@router.get("/dashboard", auth=True)
async def dashboard(request: Request):
    user = request.state.user
    return render(
        request, "authenticated/dashboard", {"user": user, "orders": await Order.get_all_async()},
        title="dashboard",               # translation key -> "Dashboard - My App"
        description="Your orders",
        robots="noindex",
        jsonld={"@context": "https://schema.org", "@type": "WebPage", "name": "Dashboard"},
        scripts=["js/chart.js"],
    )
{# resources/html/authenticated/dashboard.jinja — no extends, no blocks #}
<main class="max-w-7xl mx-auto px-4 py-8 space-y-6">
  <h1 class="text-3xl font-black">{{ translate['welcome'] }}, {{ user.name }}!</h1>
  {{ datatable(orders, ["id", ("total", "Total"), "status"], rows_per_page=10) }}
  {{ carousel([{"image": "img/promo.webp", "title": "Sale"}], autoplay=5000) }}
</main>

Templates written for 2.0.0 with {% extends %} keep working unchanged.

Forms without JavaScript

<form action="/api/contact" method="post" data-lila-form data-snackbar data-reset>
  <input name="email" type="email" required>
  <textarea name="text" required minlength="10"></textarea>
  <div data-message hidden></div>
  <button type="submit" data-loading-text="Sending...">Send</button>
</form>

<button data-action="/api/items/3" data-method="DELETE" data-confirm="Delete?" data-refresh="#items">Delete</button>

HTML5 validation, CSRF, disabled buttons with a spinner, messages from the API (success, message/msg, 422 errors per field), redirects and table refreshes are handled by lila-client.js.

Everyday UI without page scripts: data-drawer (left/right/top/bottom), data-dropdown, data-copy, data-confirm, data-toggle-class, data-remove, data-password-toggle, data-filter, data-char-count, data-autosubmit, data-tab-target, data-carousel, <time data-local-time> — see the client docs.

Models and migrations

from sqlalchemy import Column, Integer, String
from lila.core.base_model import BaseModel

class Product(BaseModel):
    __tablename__ = "products"
    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(String(120), nullable=False)
    active = Column(Integer, nullable=False, default=1)

# Async API
await Product.insert_async(name="Chair")
await Product.get_all_async(limit=50, order_by="name")
await Product.get_by_id_async(1, select="id,name")
await Product.update_async(1, name="Desk")
await Product.delete_async(1)  # soft delete when `active` exists

Run lila migrate after changing models; it adds missing columns. For destructive changes use lila migrate --refresh --yes (asks for confirmation otherwise).

Cache

from lila.core.cache import Cache

await Cache.set_async("key", {"a": 1}, ttl=300)
data = await Cache.get_async("key")
await Cache.delete_async("key")
await Cache.clear_async(include_sessions=False)

CACHE_DRIVER accepts memory (default), redis or file. Redis is probed lazily; if it is down Lila continues with the in-memory cache and logs once.

AI with PII masking

from lila.core.ia import IA

ai = IA(provider="gemini", rate_limit=20, rate_window=60, cache_ttl=3600)
result = await ai.ask(
    prompt="Draft a support reply for this customer:",
    data={"name": "Maria", "email": "maria@example.com", "ticket": "Cannot access invoice #402"},
    sensitive_keys=["name", "email"],
    request=request,
)

Set OPENAI_API_KEY, DEEPSEEK_API_KEY or GEMINI_API_KEY; override the model with OPENAI_MODEL, DEEPSEEK_MODEL or GEMINI_MODEL.

Auth + Admin

lila auth                 # login/register/reset/profile/dashboard + OAuth
lila admin -u admin -p ... # create/update the admin user
lila migrate

Admin lives at /admin; OAuth callbacks at /auth/{provider}/callback. Set APP_URL and the provider credentials in .env. Password reset requires SMTP (SMTP_HOST, SMTP_USER, SMTP_PASSWORD, SMTP_FROM) — in development the reset URL is printed to the console instead.

Desktop

pip install "lila-framework[desktop]"
lila desktop dev
lila desktop build --name MyApp --onefile --windowed

The generated executable bundles main, app, Lila templates and public/. Lila binds the desktop server to 127.0.0.1, enforces a per-run local token, prevents a second instance and stores its SQLite/cache/logs in the OS application-data directory.


🛠️ CLI Reference

Command Description
lila init Scaffold a project without overwriting existing files.
lila dev Uvicorn reload + Jinja/static SSE auto-reload (reads .env HOST/PORT).
lila run -w 2 Production server; warns when using memory cache with >1 worker.
lila doctor Health check: Python, .env, SECRET_KEY, DB, templates, public/.
lila routes List every route registered in main:app.
lila auth Auth 2.0 scaffold (backups for overwritten files).
lila admin Create/update admin user + link routes safely.
lila model create Generate an ActiveRecord model.
lila crud --model X CRUD API + HTML DataTable with CSRF.
lila migrate Create tables and add missing columns.
lila migrate --refresh --yes Drop and recreate (asks without --yes).
lila optimize Minify JS/CSS (needs the optimize extra) + WebP images.
lila secret-key Rotate SECRET_KEY.
lila seo public/sitemap.xml + robots.txt (served at /sitemap.xml).
lila worker Redis JSON task worker (pip install lila-framework[redis]).
lila deploy Generate systemd + Nginx + Windows Server files in deploy/.
lila css build Compiled, minified Tailwind CSS for production (no Node.js).
lila bench URL Requests/s, latency percentiles and server RSS.
lila tasks / lila schedule SQLite task queue and @every/@cron jobs.
lila i18n check Translation keys missing per language.
sudo "$(which lila)" deploy setup Interactive Linux install: .env for production, systemd, Nginx, Certbot HTTPS.
lila backup Online, zero-downtime SQLite backup with retention.
lila desktop dev/build Native window / PyInstaller executable.

lila desktop binds to 127.0.0.1, enforces a generated local session token and allows only one instance. The frozen build stores SQLite/cache/logs in the OS application-data directory.


🧪 Tests

python -m unittest discover -s tests -v

📖 Documentation

Full guides and API reference: https://seip25.github.io/Lila


📄 License

MIT. See LICENSE.

Security policy: SECURITY.md · Changelog: CHANGELOG.md

Metadata

Release files for lila-framework 2.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for lila-framework 2.1.0
File Size Uploaded Provenance Actions
lila_framework-2.1.0.tar.gz 901.6 kB Without provenance Details for lila_framework-2.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for lila-framework 2.1.0
File Interpreter ABI Platform Actions
lila_framework-2.1.0-py3-none-any.whl Python 3 none any Details for lila_framework-2.1.0-py3-none-any.whl

Total release size: 1.8 MB

Release files / lila_framework-2.1.0.tar.gz

Download URL lila_framework-2.1.0.tar.gz
Size 901.6 kB
Tags Source
SHA-256 checksum
How to use checksums
2d8170f3b06646af8e73b1ffe2edaed22de31c9252dafe856cdf29c317023702
BLAKE2b-256 checksum
How to use checksums
da835fb14201518d6265c38746174c0b5c92511c73e724771c3c24e198ba276d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.6

Release files / lila_framework-2.1.0-py3-none-any.whl

Download URL lila_framework-2.1.0-py3-none-any.whl
Size 921.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
45f26eb122031a950621fa4afec024672a73d33c020857baa259b2aa5407e8d7
BLAKE2b-256 checksum
How to use checksums
339cc691171f6b1b21ad4ca4b0d43a9778ad6bc6be7f749298279c66f3d0e9d6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.6

Security for lila-framework 2.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

2.1.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page