Skip to main content

A lightweight routing framework for Python Workers

Project description

Kinglet Logo

Kinglet

Lightning-fast Python web framework for Cloudflare Workers

Quick Start

Available on PyPi either: run pip install kinglet or add to pyproject.toml dependencies = ["kinglet"]

If you can't install packages: embed kinglet/kinglet.py into your worker/src or project

# Deploy to your ASGI environment
from kinglet import Kinglet

app = Kinglet(root_path="/api")

@app.post("/auth/login")
async def login(request):
    data = await request.json()
    return {"token": "jwt-token", "user": data["email"]}

Why Kinglet?

Feature Kinglet FastAPI Flask
Bundle Size 29KB 7.8MB 1.9MB
Testing No server needed Requires TestServer Requires test client
Workers Ready ✅ Built-in ❌ Complex setup ❌ Not compatible

In practical terms FastAPI's load time (especially on cold start) may exceed the worker allownace of cloudflare. Additionally Flask, Bottle and co have different expectations for the tuple that ASGI passes in.

Core Features

Root Path Support

Perfect for /api behind Cloudflare Pages:

app = Kinglet(root_path="/api")

@app.get("/users")  # Handles /api/users
async def get_users(request):
    return {"users": []}

Typed Parameters

Built-in validation for query and path parameters:

@app.get("/search")
async def search(request):
    limit = request.query_int("limit", 10)        # Returns int or 400 error
    enabled = request.query_bool("enabled", False) # Returns True/False
    tags = request.query_all("tags")              # Returns list of values

@app.get("/users/{user_id}")
async def get_user(request):
    user_id = request.path_param_int("user_id")   # Returns int or 400 error
    uuid = request.path_param_uuid("uuid")        # Validates UUID format

Authentication Helpers

Parse Bearer tokens and Basic auth automatically:

@app.get("/protected")
async def protected_route(request):
    token = request.bearer_token()        # Extract JWT from Authorization header
    user, password = request.basic_auth() # Parse Basic authentication
    is_authed = request.is_authenticated() # True if any auth present
    
    if not token:
        return Response.error("Authentication required", 401)
    return {"user": "authenticated"}

Experience APIs & Caching

R2-backed cache-aside pattern with dynamic path support:

@app.get("/games/{slug}")
@cache_aside(cache_type="game_detail", ttl=3600)
async def game_detail(request):
    return {"game": await get_game(request.path_param("slug"))}

Exception Wrapping & Access Control

Automatic error handling and endpoint restrictions:

app = Kinglet(debug=True)  # Auto-wraps exceptions with request IDs

@app.get("/admin/debug")
@require_dev()  # 403 in production
@geo_restrict(allowed=["US", "CA"]) 
async def debug_endpoint(request):
    raise ValueError("Auto-wrapped with context")

Zero-Dependency Testing

Test without HTTP servers - runs in <1ms:

def test_my_api():
    client = TestClient(app)
    
    status, headers, body = client.request("GET", "/search?limit=5&enabled=true")
    assert status == 200
    
    status, headers, body = client.request("GET", "/protected", headers={
        "Authorization": "Bearer jwt-token-123"
    })
    assert status == 200

Learn More

Production Ready

  • Request ID tracing for debugging
  • Typed parameter validation (int, bool, UUID)
  • Built-in authentication helpers (Bearer, Basic auth)
  • Automatic exception wrapping with environment-aware details
  • Access control decorators (dev-only, geo-restrictions)
  • R2 cache-aside pattern for Experience APIs
  • Environment-aware media URLs (dev vs production)
  • Request validation decorators (JSON body, required fields)
  • Configurable CORS for security
  • Error boundaries with proper status codes
  • Debug mode for development
  • Type hints for better DX
  • Zero-dependency testing with TestClient

Contributing

Built for the Cloudflare Workers Python community. PRs welcome for:

  • Performance optimizations
  • Additional middleware patterns
  • Better TypeScript integration
  • More testing utilities

Need help? Check the docs or open an issue.


Full API Example

from kinglet import Kinglet, Response, TestClient

# Create app with root path for /api endpoints
app = Kinglet(root_path="/api", debug=True)

@app.get("/")
async def health_check(request):
    return {"status": "healthy", "request_id": request.request_id}

@app.post("/auth/register") 
async def register(request):
    data = await request.json()
    
    if not data.get("email"):
        return Response.error("Email required", status=400, 
                            request_id=request.request_id)
    
    # Simulate user creation
    return Response.json({
        "user_id": "123",
        "email": data["email"], 
        "created": True
    }, request_id=request.request_id)

@app.get("/users/{user_id}")
async def get_user(request):
    # Typed path parameter with validation
    user_id = request.path_param_int("user_id")  # Returns int or 400 error
    
    # Check authentication
    token = request.bearer_token()
    if not token:
        return Response.error("Authentication required", status=401,
                            request_id=request.request_id)
    
    # Access environment (Cloudflare bindings) 
    db = request.env.DB
    user = await db.prepare("SELECT * FROM users WHERE id = ?").bind(user_id).first()
    
    if not user:
        return Response.error("User not found", status=404,
                            request_id=request.request_id) 
    
    return {"user": user.to_py(), "token": token}

@app.get("/search")
async def search_users(request):
    # Typed query parameters
    page = request.query_int("page", 1)
    limit = request.query_int("limit", 10) 
    active_only = request.query_bool("active", False)
    tags = request.query_all("tags")
    
    return {
        "users": [f"user_{i}" for i in range((page-1)*limit, page*limit)],
        "filters": {"active": active_only, "tags": tags},
        "pagination": {"page": page, "limit": limit}
    }


# Production: Cloudflare Workers entry point
async def on_fetch(request, env):
    return await app(request, env)

# Development: Test without server
if __name__ == "__main__":
    client = TestClient(app)
    
    # Test health check
    status, headers, body = client.request("GET", "/")
    print(f"Health: {status} - {body}")
    
    # Test registration  
    status, headers, body = client.request("POST", "/auth/register", json={
        "email": "test@example.com",
        "password": "secure123"
    })
    print(f"Register: {status} - {body}")
    
    # Test authenticated user lookup
    status, headers, body = client.request("GET", "/users/42", headers={
        "Authorization": "Bearer user-token-123"
    })
    print(f"User: {status} - {body}")
    
    # Test typed query parameters
    status, headers, body = client.request("GET", "/search?page=2&limit=5&active=true&tags=python")
    print(f"Search: {status} - {body}")
    
    # Test error handling
    status, headers, body = client.request("POST", "/auth/register", json={})
    print(f"Error: {status} - {body}")

Output:

Health: 200 - {"status": "healthy", "request_id": "a1b2c3d4"}
Register: 200 - {"user_id": "123", "email": "test@example.com", "created": true, "request_id": "e5f6g7h8"}
User: 200 - {"user": {"id": 42, "email": "test@example.com"}, "token": "user-token-123"}
Search: 200 - {"users": ["user_5", "user_6", "user_7", "user_8", "user_9"], "filters": {"active": true, "tags": ["python"]}, "pagination": {"page": 2, "limit": 5}}
Error: 400 - {"error": "Email required", "status_code": 400, "request_id": "i9j0k1l2"}

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

kinglet-1.2.0.tar.gz (27.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

kinglet-1.2.0-py3-none-any.whl (15.3 kB view details)

Uploaded Python 3

File details

Details for the file kinglet-1.2.0.tar.gz.

File metadata

  • Download URL: kinglet-1.2.0.tar.gz
  • Upload date:
  • Size: 27.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.10.16

File hashes

Hashes for kinglet-1.2.0.tar.gz
Algorithm Hash digest
SHA256 b8d6ddd4187dd655ac4bbf2e57913c864343c656a479df1050a89b86629b50a9
MD5 95966d1f91b7296f1b392a1162714d20
BLAKE2b-256 a564fce9c58fc474392cfc6618f3d9586282dbc4822d63c7e943289b12c39e91

See more details on using hashes here.

File details

Details for the file kinglet-1.2.0-py3-none-any.whl.

File metadata

  • Download URL: kinglet-1.2.0-py3-none-any.whl
  • Upload date:
  • Size: 15.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.10.16

File hashes

Hashes for kinglet-1.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 4d58f5d7075219af982409a7f46800fce26d9bf25a6e5d920a0e9abb5323a06e
MD5 69c8f2bb46d9797f317d1ac5fece0f96
BLAKE2b-256 885d5948cb041fa7337ce371d1ea9808b5c5e06528c502f83241ceb24f9f3f19

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page