High-performance Python web framework with a Rust core. Build REST APIs, WebSockets, and SSE services with FastAPI/Litestar-style decorators backed by Axum and Tower-HTTP. 2.8x faster than FastAPI.
Project description
Spikard Python
High-performance Python web framework with a Rust core. Build REST APIs, WebSockets, and SSE services with FastAPI/Litestar-style decorators backed by Axum and Tower-HTTP.
Badges
Installation
Via pip:
pip install spikard
Pre-built wheels are available for macOS, Linux, and Windows. If a wheel isn't available for your platform, pip will build from source (requires Rust toolchain).
From source (development):
cd packages/python
uv sync
# or
pip install -e .
Requirements:
- Python 3.10+
- Rust toolchain (only required for building from source)
Quick Start
from spikard import Spikard
from msgspec import Struct
class User(Struct):
id: int
name: str
email: str
app = Spikard()
@app.get("/users/{user_id}")
async def get_user(user_id: int) -> User:
return User(id=user_id, name="Alice", email="alice@example.com")
@app.post("/users")
async def create_user(user: User) -> User:
# Automatic validation via msgspec
return user
if __name__ == "__main__":
app.run(port=8000)
Core Features
Route Registration
Spikard supports both FastAPI-style (instance decorators) and Litestar-style (standalone decorators) patterns.
FastAPI-style (instance decorators):
from spikard import Spikard
app = Spikard()
@app.get("/users")
async def list_users():
return {"users": []}
@app.post("/users")
async def create_user(user: User):
return user
Litestar-style (standalone decorators):
from spikard import Spikard, get, post, put, patch, delete
app = Spikard()
@get("/users")
async def list_users():
return {"users": []}
@post("/users")
async def create_user(user: User):
return user
@put("/users/{user_id}")
async def update_user(user_id: int, user: User):
return user
@patch("/users/{user_id}")
async def patch_user(user_id: int, updates: dict):
return updates
@delete("/users/{user_id}")
async def delete_user(user_id: int):
return {"deleted": True}
All HTTP methods supported:
@app.get()/@get()- GET requests@app.post()/@post()- POST requests@app.put()/@put()- PUT requests@app.patch()/@patch()- PATCH requests@app.delete()/@delete()- DELETE requests@app.head()/@head()- HEAD requests@app.options()/@options()- OPTIONS requests@app.trace()/@trace()- TRACE requests
Path Parameters
@app.get("/users/{user_id}")
async def get_user(user_id: int):
return {"id": user_id}
@app.get("/posts/{post_id}/comments/{comment_id}")
async def get_comment(post_id: int, comment_id: int):
return {"post_id": post_id, "comment_id": comment_id}
Query Parameters
from spikard import Query
@app.get("/search")
async def search(
q: str,
limit: int = Query(default=10),
offset: int = Query(default=0)
):
return {"query": q, "limit": limit, "offset": offset}
Request Body Validation
Spikard supports multiple validation libraries. msgspec is the default and recommended for best performance.
With msgspec.Struct (recommended - fastest):
from msgspec import Struct
class CreatePost(Struct):
title: str
content: str
tags: list[str] = []
@app.post("/posts")
async def create_post(post: CreatePost):
return {"title": post.title, "tag_count": len(post.tags)}
Supported validation libraries:
- msgspec.Struct (default, zero-copy, fastest)
- Pydantic v2 BaseModel
- dataclasses
Dependency Injection
Register values or factories and inject by parameter name:
from spikard import Spikard
from spikard.di import Provide
app = Spikard()
app.provide("config", {"db_url": "postgresql://localhost/app"})
app.provide("db", Provide(lambda config: f"pool({config['db_url']})", depends_on=["config"], singleton=True))
@app.get("/stats")
async def stats(config: dict[str, str], db: str):
return {"db": db, "env": config["db_url"]}
- TypedDict
- NamedTuple
- attrs classes
With Pydantic v2:
from pydantic import BaseModel, EmailStr
class User(BaseModel):
name: str
email: EmailStr
@app.post("/users")
async def create_user(user: User):
return user.model_dump()
With dataclasses:
from dataclasses import dataclass
@dataclass
class Product:
name: str
price: float
@app.post("/products")
async def create_product(product: Product):
return product
With plain JSON Schema dict:
user_schema = {
"type": "object",
"properties": {
"name": {"type": "string"},
"email": {"type": "string", "format": "email"}
},
"required": ["name", "email"]
}
@app.post("/users", request_schema=user_schema)
async def create_user(user: dict):
# user is validated against schema
return user
File Uploads
from spikard import UploadFile
@app.post("/upload")
async def upload_file(file: UploadFile):
# Use aread() for async file reading
content = await file.aread()
return {
"filename": file.filename,
"size": file.size,
"content_type": file.content_type,
"content_length": len(content)
}
Custom Responses
from spikard import Response
@app.post("/users")
async def create_user(user: User) -> Response:
return Response(
content=user,
status_code=201,
headers={"X-Custom": "value"}
)
Streaming Responses
from spikard import StreamingResponse
async def generate_data():
for i in range(10):
yield f"data: {i}\n".encode()
@app.get("/stream")
async def stream():
return StreamingResponse(generate_data())
Configuration
from spikard import Spikard, ServerConfig, CompressionConfig, RateLimitConfig
config = ServerConfig(
host="0.0.0.0",
port=8080,
workers=4,
enable_request_id=True,
max_body_size=10 * 1024 * 1024, # 10 MB
request_timeout=30,
compression=CompressionConfig(
gzip=True,
brotli=True,
quality=6
),
rate_limit=RateLimitConfig(
per_second=100,
burst=200
)
)
app = Spikard(config=config)
Middleware Configuration
Compression:
from spikard import CompressionConfig
compression = CompressionConfig(
gzip=True, # Enable gzip
brotli=True, # Enable brotli
min_size=1024, # Min bytes to compress
quality=6 # 0-11 for brotli, 0-9 for gzip
)
Rate Limiting:
from spikard import RateLimitConfig
rate_limit = RateLimitConfig(
per_second=100, # Max requests per second
burst=200, # Burst allowance
ip_based=True # Per-IP rate limiting
)
JWT Authentication:
from spikard import JwtConfig
jwt = JwtConfig(
secret="your-secret-key",
algorithm="HS256", # HS256, HS384, HS512, RS256, etc.
audience=["api.example.com"],
issuer="auth.example.com",
leeway=30 # seconds
)
Static Files:
from spikard import StaticFilesConfig
static = StaticFilesConfig(
directory="./public",
route_prefix="/static",
index_file=True,
cache_control="public, max-age=3600"
)
config = ServerConfig(static_files=[static])
OpenAPI Documentation:
from spikard.config import OpenApiConfig
openapi = OpenApiConfig(
enabled=True,
title="My API",
version="1.0.0",
description="API documentation",
swagger_ui_path="/docs",
redoc_path="/redoc"
)
config = ServerConfig(openapi=openapi)
Lifecycle Hooks
@app.on_request
async def log_request(request):
print(f"{request.method} {request.path}")
return request # Must return request to continue
@app.pre_validation
async def check_auth(request):
token = request.headers.get("Authorization")
if not token:
return Response({"error": "Unauthorized"}, status_code=401)
return request
@app.pre_handler
async def rate_check(request):
# Additional checks before handler
return request
@app.on_response
async def add_headers(response):
response.headers["X-Frame-Options"] = "DENY"
return response
@app.on_error
async def log_error(response):
print(f"Error: {response.status_code}")
return response
WebSockets
from spikard import Spikard, websocket
app = Spikard()
@websocket("/ws")
async def chat_handler(message: dict) -> dict | None:
"""Handle incoming WebSocket messages."""
print(f"Received: {message}")
# Echo back the message
return {"echo": message}
Server-Sent Events (SSE)
from spikard import Spikard, sse
import asyncio
app = Spikard()
@sse("/events")
async def event_stream():
"""Stream events to connected clients."""
for i in range(10):
await asyncio.sleep(1)
yield {
"count": i,
"message": f"Event {i}",
"timestamp": i
}
Background Tasks
from spikard import background
async def heavy_processing(data: dict):
"""Async function that runs after response is sent."""
# Heavy processing here
pass
@app.post("/process")
async def process_data(data: dict):
# Schedule async function to run in background
background.run(heavy_processing(data))
return {"status": "processing"}
Testing
from spikard import TestClient
import pytest
@pytest.fixture
def client():
return TestClient(app)
@pytest.mark.asyncio
async def test_get_user(client):
response = await client.get("/users/123")
assert response.status_code == 200
data = response.json()
assert data["id"] == 123
@pytest.mark.asyncio
async def test_create_user(client):
response = await client.post("/users", json={
"name": "Alice",
"email": "alice@example.com"
})
assert response.status_code == 201
WebSocket Testing
import json
@pytest.mark.asyncio
async def test_websocket(client):
async with client.websocket("/ws") as ws:
await ws.send(json.dumps({"message": "hello"}))
response_text = await ws.recv()
response = json.loads(response_text)
assert response["echo"]["message"] == "hello"
SSE Testing
@pytest.mark.asyncio
async def test_sse(client):
async with client.sse("/events") as sse:
events = []
async for event in sse:
events.append(event.data)
if len(events) >= 3:
break
assert len(events) == 3
Type Support
Spikard automatically extracts JSON schemas from:
- msgspec.Struct (recommended, fastest)
- Pydantic v2 BaseModel
- dataclasses
- TypedDict
- NamedTuple
All compile to JSON Schema for validation and OpenAPI generation.
Performance
vs FastAPI
Comprehensive comparison across 18 real-world workloads on Apple M4 Pro (100 concurrent connections):
| Framework | Avg Throughput | Mean Latency | Difference |
|---|---|---|---|
| Spikard | 35,779 req/s | 7.44ms | baseline |
| FastAPI | 12,776 req/s | 7.90ms | -64% slower |
Spikard is 2.8x faster than FastAPI with statistically significant improvements (p < 0.05).
Why Spikard is Faster
Rust-Powered Core:
- HTTP server built on Tokio and Hyper
- Tower middleware for zero-overhead routing
- No Python GIL contention for HTTP layer
Zero-Copy Optimizations:
- Direct PyO3 type construction (no JSON string serialization)
- Eliminates 30-40% conversion overhead vs serialize-then-parse
- msgspec integration for validation without extra allocations
Async-First Design:
pyo3_async_runtimesfor efficient coroutine handling- Single event loop initialization per worker
- GIL release before awaiting Rust futures
Running the Server
# Development
app.run(host="127.0.0.1", port=8000)
# Production with multiple workers
config = ServerConfig(
host="0.0.0.0",
port=8080,
workers=4
)
app.run(config=config)
Examples
The examples directory contains comprehensive demonstrations:
Python-specific examples:
- Dependency Injection - Basic DI patterns
- Database Integration - DI with database pools
- Additional examples in examples/
API Schemas (language-agnostic, can be used with code generation):
- Todo API - REST CRUD with validation
- File Service - File uploads/downloads
- Auth Service - JWT, API keys, OAuth
- Chat Service - WebSocket messaging
- Event Streams - SSE streaming
See examples/README.md for code generation instructions.
Documentation
- Main Project README
- Contributing Guide
- Architecture Decision Records
- Python Examples
- GitHub Discussions
Ecosystem Links
- Rust: Crates.io
- Node.js: npm (@spikard/node)
- Ruby: RubyGems
- PHP: Packagist
- WebAssembly: npm (@spikard/wasm)
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file spikard-0.3.5.tar.gz.
File metadata
- Download URL: spikard-0.3.5.tar.gz
- Upload date:
- Size: 309.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7c1cf128b72bdd9d3763dbac4d93572f426914eca97875e0570774627fdf4f1b
|
|
| MD5 |
6efe7b63c5ecb6d749b60b2adca51b42
|
|
| BLAKE2b-256 |
b7e67a3db003e4be6e8cbdf2bc3cd7893267ef095ec3dd7a34a264193779cf23
|
Provenance
The following attestation bundles were made for spikard-0.3.5.tar.gz:
Publisher:
publish.yaml on Goldziher/spikard
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
spikard-0.3.5.tar.gz -
Subject digest:
7c1cf128b72bdd9d3763dbac4d93572f426914eca97875e0570774627fdf4f1b - Sigstore transparency entry: 748696489
- Sigstore integration time:
-
Permalink:
Goldziher/spikard@f0b0294cdc48eb21578b7cb5906d1a53afa88697 -
Branch / Tag:
refs/tags/v0.3.5 - Owner: https://github.com/Goldziher
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yaml@f0b0294cdc48eb21578b7cb5906d1a53afa88697 -
Trigger Event:
release
-
Statement type:
File details
Details for the file spikard-0.3.5-cp310-abi3-win_amd64.whl.
File metadata
- Download URL: spikard-0.3.5-cp310-abi3-win_amd64.whl
- Upload date:
- Size: 5.6 MB
- Tags: CPython 3.10+, Windows x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3008c28ea0f78d951d6c7c4df9bf9935233707fecff412c0d07978c0bbbc62b1
|
|
| MD5 |
af363b3ee52610ad0ff45db15729b9ee
|
|
| BLAKE2b-256 |
c4f27b9549468f6c5a1566f8ad6b5664e9fc1ece92bdf514be29081ac6942871
|
Provenance
The following attestation bundles were made for spikard-0.3.5-cp310-abi3-win_amd64.whl:
Publisher:
publish.yaml on Goldziher/spikard
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
spikard-0.3.5-cp310-abi3-win_amd64.whl -
Subject digest:
3008c28ea0f78d951d6c7c4df9bf9935233707fecff412c0d07978c0bbbc62b1 - Sigstore transparency entry: 748696491
- Sigstore integration time:
-
Permalink:
Goldziher/spikard@f0b0294cdc48eb21578b7cb5906d1a53afa88697 -
Branch / Tag:
refs/tags/v0.3.5 - Owner: https://github.com/Goldziher
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yaml@f0b0294cdc48eb21578b7cb5906d1a53afa88697 -
Trigger Event:
release
-
Statement type:
File details
Details for the file spikard-0.3.5-cp310-abi3-manylinux_2_34_x86_64.whl.
File metadata
- Download URL: spikard-0.3.5-cp310-abi3-manylinux_2_34_x86_64.whl
- Upload date:
- Size: 5.5 MB
- Tags: CPython 3.10+, manylinux: glibc 2.34+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
729a31101c0cafec26fcb8be51f77ecad6ff0d905445501c137fbfa01be6ca2d
|
|
| MD5 |
ce79bbcd5ecf3b841fa5545be181447d
|
|
| BLAKE2b-256 |
363dc99dee2196fa2290b17ebaa5a17f452c9110f214adfea641f6da2d755b7f
|
Provenance
The following attestation bundles were made for spikard-0.3.5-cp310-abi3-manylinux_2_34_x86_64.whl:
Publisher:
publish.yaml on Goldziher/spikard
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
spikard-0.3.5-cp310-abi3-manylinux_2_34_x86_64.whl -
Subject digest:
729a31101c0cafec26fcb8be51f77ecad6ff0d905445501c137fbfa01be6ca2d - Sigstore transparency entry: 748696494
- Sigstore integration time:
-
Permalink:
Goldziher/spikard@f0b0294cdc48eb21578b7cb5906d1a53afa88697 -
Branch / Tag:
refs/tags/v0.3.5 - Owner: https://github.com/Goldziher
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yaml@f0b0294cdc48eb21578b7cb5906d1a53afa88697 -
Trigger Event:
release
-
Statement type:
File details
Details for the file spikard-0.3.5-cp310-abi3-macosx_14_0_arm64.whl.
File metadata
- Download URL: spikard-0.3.5-cp310-abi3-macosx_14_0_arm64.whl
- Upload date:
- Size: 5.0 MB
- Tags: CPython 3.10+, macOS 14.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5bfc0396c72fb14a2964cacc3fc4ae88dd165ff99c59b74aaba9662aad7041b5
|
|
| MD5 |
d532978a567c1e16340a82b0958d5e19
|
|
| BLAKE2b-256 |
d806538e1ad139df0bfaadb622fbc9bed419dcbe44399b747d80b7dfb5284b8f
|
Provenance
The following attestation bundles were made for spikard-0.3.5-cp310-abi3-macosx_14_0_arm64.whl:
Publisher:
publish.yaml on Goldziher/spikard
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
spikard-0.3.5-cp310-abi3-macosx_14_0_arm64.whl -
Subject digest:
5bfc0396c72fb14a2964cacc3fc4ae88dd165ff99c59b74aaba9662aad7041b5 - Sigstore transparency entry: 748696490
- Sigstore integration time:
-
Permalink:
Goldziher/spikard@f0b0294cdc48eb21578b7cb5906d1a53afa88697 -
Branch / Tag:
refs/tags/v0.3.5 - Owner: https://github.com/Goldziher
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yaml@f0b0294cdc48eb21578b7cb5906d1a53afa88697 -
Trigger Event:
release
-
Statement type: