Scaffold a production-ready Litestar 2.x application with class-based controllers, DTOs, guards, SQLAlchemy plugin, and auto-generated OpenAPI docs.
73
64%
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Passed
No known issues
Optimize this skill with Tessl
npx tessl skill review --optimize ./backend-python/litestar-project-starter/SKILL.mdScaffold a production-ready Litestar 2.x application with class-based controllers, DTOs, guards, SQLAlchemy plugin, and auto-generated OpenAPI docs.
uv or pip for dependency managementmkdir -p src/app/{controllers,models,dtos,guards,middleware,services,db} tests
touch src/app/__init__.py src/app/controllers/__init__.py src/app/models/__init__.py \
src/app/dtos/__init__.py src/app/guards/__init__.py src/app/middleware/__init__.py \
src/app/services/__init__.py src/app/db/__init__.py tests/__init__.py
python -m venv .venv && source .venv/bin/activate
cat > pyproject.toml << 'PYPROJECT'
[project]
name = "app"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = [
"litestar[standard]>=2.14",
"advanced-alchemy>=0.28",
"asyncpg>=0.30",
"pydantic>=2.9",
"pydantic-settings>=2.6",
"structlog>=24.4",
]
[project.optional-dependencies]
dev = [
"pytest>=8.3",
"pytest-asyncio>=0.24",
"ruff>=0.8",
]
[tool.ruff]
target-version = "py312"
line-length = 99
[tool.ruff.lint]
select = ["E", "F", "I", "N", "UP", "B", "SIM", "ASYNC"]
[tool.pytest.ini_options]
asyncio_mode = "auto"
testpaths = ["tests"]
PYPROJECTproject-root/
├── pyproject.toml
├── .env.example # Required env vars template
├── src/
│ └── app/
│ ├── __init__.py
│ ├── main.py # Application factory + lifespan
│ ├── config.py # Pydantic Settings
│ ├── controllers/
│ │ ├── __init__.py
│ │ ├── health.py
│ │ └── users.py
│ ├── models/
│ │ ├── __init__.py
│ │ └── user.py # SQLAlchemy ORM models
│ ├── dtos/
│ │ ├── __init__.py
│ │ └── user.py # Litestar DTOs
│ ├── guards/
│ │ ├── __init__.py
│ │ └── auth.py
│ ├── middleware/
│ │ ├── __init__.py
│ │ └── logging.py
│ ├── services/
│ │ ├── __init__.py
│ │ └── user.py
│ └── db/
│ ├── __init__.py
│ └── plugin.py # SQLAlchemy plugin config
└── tests/
├── __init__.py
├── conftest.py
└── test_users.pyProvide to register dependencies and type hints to inject them.advanced-alchemy) manages engine, sessions, and repository pattern./schema (Swagger UI, ReDoc, Stoplight Elements all available).from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8")
app_name: str = "app"
debug: bool = False
database_url: str = "postgresql+asyncpg://postgres:postgres@localhost:5432/app"
cors_origins: list[str] = ["http://localhost:3000"]
secret_key: str = "change-me-in-production"
settings = Settings()from datetime import datetime
from uuid import UUID
from advanced_alchemy.base import UUIDAuditBase
from sqlalchemy.orm import Mapped, mapped_column
class User(UUIDAuditBase):
__tablename__ = "users"
email: Mapped[str] = mapped_column(unique=True, index=True)
name: Mapped[str]
is_active: Mapped[bool] = mapped_column(default=True)from litestar.contrib.sqlalchemy.dto import SQLAlchemyDTO, SQLAlchemyDTOConfig
from app.models.user import User
class UserReadDTO(SQLAlchemyDTO[User]):
config = SQLAlchemyDTOConfig(
exclude={"updated_at"},
)
class UserCreateDTO(SQLAlchemyDTO[User]):
config = SQLAlchemyDTOConfig(
include={"email", "name"},
)
class UserUpdateDTO(SQLAlchemyDTO[User]):
config = SQLAlchemyDTOConfig(
include={"name", "is_active"},
partial=True,
)from advanced_alchemy.repository import SQLAlchemyAsyncRepository
from advanced_alchemy.service import SQLAlchemyAsyncRepositoryService
from sqlalchemy.ext.asyncio import AsyncSession
from app.models.user import User
class UserRepository(SQLAlchemyAsyncRepository[User]):
model_type = User
class UserService(SQLAlchemyAsyncRepositoryService[User]):
repository_type = UserRepository
async def provide_user_service(db_session: AsyncSession) -> UserService:
return UserService(session=db_session)from litestar.connection import ASGIConnection
from litestar.exceptions import NotAuthorizedException
from litestar.handlers import BaseRouteHandler
def require_active_user(connection: ASGIConnection, _: BaseRouteHandler) -> None:
"""Guard that ensures the request has a valid authenticated user."""
if not connection.scope.get("user"):
raise NotAuthorizedException(detail="Authentication required")from uuid import UUID
from litestar import Controller, delete, get, patch, post
from litestar.di import Provide
from litestar.dto import DTOData
from litestar.params import Parameter
from app.dtos.user import UserCreateDTO, UserReadDTO, UserUpdateDTO
from app.models.user import User
from app.services.user import UserService, provide_user_service
class UserController(Controller):
path = "/users"
tags = ["Users"]
dependencies = {"user_service": Provide(provide_user_service)}
return_dto = UserReadDTO
@get("/")
async def list_users(self, user_service: UserService) -> list[User]:
results, _ = await user_service.list_and_count()
return results
@get("/{user_id:uuid}")
async def get_user(self, user_service: UserService, user_id: UUID) -> User:
return await user_service.get(user_id)
@post("/", dto=UserCreateDTO)
async def create_user(self, user_service: UserService, data: User) -> User:
return await user_service.create(data)
@patch("/{user_id:uuid}", dto=UserUpdateDTO)
async def update_user(
self, user_service: UserService, user_id: UUID, data: DTOData[User]
) -> User:
return await user_service.update(item_id=user_id, data=data.as_builtins())
@delete("/{user_id:uuid}")
async def delete_user(self, user_service: UserService, user_id: UUID) -> None:
await user_service.delete(user_id)from litestar import Controller, get
class HealthController(Controller):
path = "/health"
tags = ["Health"]
@get("/")
async def health_check(self) -> dict[str, str]:
return {"status": "ok"}from advanced_alchemy.extensions.litestar import (
AsyncSessionConfig,
SQLAlchemyAsyncConfig,
SQLAlchemyPlugin,
)
from app.config import settings
sqlalchemy_config = SQLAlchemyAsyncConfig(
connection_string=settings.database_url,
session_config=AsyncSessionConfig(expire_on_commit=False),
)
sqlalchemy_plugin = SQLAlchemyPlugin(config=sqlalchemy_config)from litestar.middleware import AbstractMiddleware
from litestar.types import ASGIApp, Receive, Scope, Send
import structlog
logger = structlog.get_logger()
class RequestLoggingMiddleware(AbstractMiddleware):
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
if scope["type"] == "http":
await logger.ainfo(
"request_started",
method=scope["method"],
path=scope["path"],
)
await self.app(scope, receive, send)from litestar import Request, Response
from litestar.status_codes import HTTP_500_INTERNAL_SERVER_ERROR
import structlog
logger = structlog.get_logger()
async def internal_server_error_handler(request: Request, exc: Exception) -> Response:
logger.error("unhandled_exception", path=request.url.path, error=str(exc))
return Response(
content={"error": "Internal Server Error"},
status_code=HTTP_500_INTERNAL_SERVER_ERROR,
)from collections.abc import AsyncGenerator
from contextlib import asynccontextmanager
import structlog
from litestar import Litestar, Request, Response
from litestar.config.cors import CORSConfig
from litestar.openapi import OpenAPIConfig
from litestar.status_codes import HTTP_500_INTERNAL_SERVER_ERROR
from app.config import settings
from app.controllers.health import HealthController
from app.controllers.users import UserController
from app.db.plugin import sqlalchemy_plugin
from app.middleware.logging import RequestLoggingMiddleware
logger = structlog.get_logger()
async def internal_server_error_handler(request: Request, exc: Exception) -> Response:
logger.error("unhandled_exception", path=request.url.path, error=str(exc))
return Response(
content={"error": "Internal Server Error"},
status_code=HTTP_500_INTERNAL_SERVER_ERROR,
)
@asynccontextmanager
async def lifespan(app: Litestar) -> AsyncGenerator[None, None]:
logger.info("application_startup", app_name=settings.app_name)
yield
logger.info("application_shutdown")
app = Litestar(
route_handlers=[
HealthController,
UserController,
],
plugins=[sqlalchemy_plugin],
middleware=[RequestLoggingMiddleware],
openapi_config=OpenAPIConfig(
title=settings.app_name,
version="0.1.0",
path="/schema",
),
cors_config=CORSConfig(allow_origins=settings.cors_origins),
lifespan=[lifespan],
exception_handlers={500: internal_server_error_handler},
debug=settings.debug,
)import pytest
from litestar.testing import AsyncTestClient
from app.main import app
@pytest.fixture
async def client():
async with AsyncTestClient(app=app) as client:
yield clientimport pytest
@pytest.mark.asyncio
async def test_health(client):
response = await client.get("/health/")
assert response.status_code == 200
assert response.json() == {"status": "ok"}
@pytest.mark.asyncio
async def test_create_user(client):
response = await client.post("/users/", json={
"email": "new@example.com",
"name": "New User",
})
assert response.status_code == 201
data = response.json()
assert data["email"] == "new@example.com".env.example to .env and fill in valuespython -m venv .venv && source .venv/bin/activatepip install -e ".[dev]" (or uv sync)alembic upgrade headuvicorn app.main:app --reload --host 0.0.0.0 --port 8000 --app-dir srccurl http://localhost:8000/schema/swagger# Install dependencies
uv sync # or: pip install -e ".[dev]"
# Run development server
litestar --app src/app/main:app run --reload --host 0.0.0.0 --port 8000
# Or with uvicorn directly
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 --app-dir src
# Database migrations (via advanced-alchemy CLI or manual Alembic)
# advanced-alchemy uses Alembic under the hood
alembic revision --autogenerate -m "add users table"
alembic upgrade head
# Run tests
pytest
# Lint and format
ruff check src tests
ruff format src tests
# View OpenAPI docs
# Swagger UI: http://localhost:8000/schema/swagger
# ReDoc: http://localhost:8000/schema/redoc
# Stoplight Elements: http://localhost:8000/schema/elements/schema/openapi.json. Use codegen tools (openapi-typescript-codegen, orval) to produce typed clients.uvicorn app.main:app --host 0.0.0.0 --port 8000 as the entrypoint. For multiple workers, use gunicorn -k uvicorn.workers.UvicornWorker.ruff check, pytest, and alembic upgrade head (against a test DB) in the pipeline.BackgroundTask and BackgroundTasks support on response objects. For heavier workloads, pair with arq or SAQ.litestar.config.cache.CacheConfig with a Redis backend for response caching.AbstractAuthenticationMiddleware for custom JWT/session auth. Combine with guards for per-route authorization.181fcbc
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.