AI-API-first async HTTP framework with Cython hot paths
Project description
🔥 Ember
AI-API-first async HTTP framework for Python — built for LLM workloads with Cython hot paths, multi-process workers, and first-class streaming.
📖 Documentation · 🤝 Contributing
Why Ember
| Ember | FastAPI | Express | NestJS | |
|---|---|---|---|---|
| Protocol | llhttp + Cython + io_uring | ASGI / uvicorn | Node.js http | Node.js http |
| Workers | Fork + SO_REUSEPORT | Single process | cluster | cluster |
| SSE streaming | Native, zero-copy | via starlette | manual | manual |
| AI primitives | Built-in | none | none | none |
Hello-world benchmark (single worker, k6 200 VUs / 20 s)
GET /hello → "Hello, World!", all running on the same Intel i7-14700 box,
0% error rate. Fiber pinned to one core (GOMAXPROCS=1) for fairness.
| Framework | RPS | p50 (ms) | p99 (ms) | peak RSS |
|---|---|---|---|---|
| Fiber (Go) | 140,993 | 1.21 | 3.96 | 9 MB |
| Ember | 112,177 | 1.68 | 4.35 | 25 MB |
| Express (Node) | 26,357 | 7.09 | 13.57 | 131 MB |
| NestJS (Node) | 23,528 | 8.08 | 13.75 | 158 MB |
| FastAPI (Python) | 17,517 | 9.45 | 30.86 | 49 MB |
Ember runs 6.4× FastAPI, 4.3× Express, and 4.8× NestJS on the same hardware — and at 112k RPS / 25 MB RSS / 80% of Go Fiber's throughput, it is the only Python framework in this league.
CRUD benchmark (PostgreSQL, mixed reads + writes)
300-VU ramp, 196k-row table, 70k requests, 0% errors:
| Framework | avg latency | p95 | p99 |
|---|---|---|---|
| Ember | 19.85 ms | 44 ms | 59 ms |
| Express | 19.20 ms | 43 ms | 56 ms |
| FastAPI | 153.72 ms | 549 ms | 794 ms |
Ember matches Node on real CRUD — within 3% of Express on average, within 6% on p99 — and has a 13× tighter tail than FastAPI.
Memory: Peak RSS dropped from 48 MB → 25 MB (-48%) in v0.2 — see Performance § Tuning the buffer pool. Idle RSS is ~22 MB, well under FastAPI (47 MB) and 5× lighter than Node frameworks.
Reproducible: see taskbench/hello_bench/ — run
./bench_all.sh.
Features
- Cython hot paths — headers, router, request, response, protocol all compiled to C
- Multi-process workers — fork-based with
SO_REUSEPORT, kernel load-balancing workers=1in-process — no supervisor overhead when you don't need it (~22 MB saved)- Tunable io_uring buffer pool —
Ember.run(io_uring_num_bufs=, io_uring_buf_size=)to scale RAM down to 2 MB pool (default) or up to 32 MB for high-concurrency workloads - Lazy AI / cache / middleware imports —
import emberno longer pulls numpy/redis/memcached for plain HTTP apps - AI-first routing —
@app.ai_route()with streaming, tool calling, conversation context - SSE streaming —
SSEResponse,sse_stream(),TokenStreamResponsefor LLM token output - Pluggable caching —
StaticCache,RedisCache,MemcachedCachevia single decorator arg - Token rate limiting —
TokenBucket,GlobalTokenBucket,RateLimitMiddleware - Model routing —
ModelRouterwith fallback, cost, and latency strategies - Semantic cache — vector-search cache for AI responses
- Built-in middleware — CORS, Bearer auth, API key
- Blueprints — modular route groups with URL prefixes
- Cross-platform — Linux/macOS multi-process, Windows single-process fallback
Install
pip install ember-api
# With all performance extras:
pip install "ember-api[fast]" # uvloop + orjson
pip install "ember-api[cache]" # Redis + Memcached backends
pip install "ember-api[all]" # everything
Build Cython extensions from source (optional, for maximum speed):
pip install cython
python setup.py build_ext --inplace
Hello World
from ember import Ember
app = Ember()
@app.get("/")
async def index():
return {"hello": "world"}
app.run(host="0.0.0.0", port=8000, workers=4)
Full CRUD Example
from ember import Ember, Blueprint, Request, JSONResponse, StaticCache, RedisCache
app = Ember()
tasks: dict = {}
@app.get("/tasks", cache=RedisCache(ttl=30))
async def list_tasks(request: Request) -> JSONResponse:
page = int(request.args.get("page", 1))
limit = int(request.args.get("limit", 10))
items = list(tasks.values())[(page - 1) * limit : page * limit]
return JSONResponse({"tasks": items, "total": len(tasks)})
@app.get("/tasks/{task_id:str}")
async def get_task(request: Request, task_id: str) -> JSONResponse:
task = tasks.get(task_id)
if not task:
return JSONResponse({"error": "not found"}, status_code=404)
return JSONResponse(task)
@app.post("/tasks")
async def create_task(request: Request) -> JSONResponse:
data = await request.json()
import uuid
task_id = str(uuid.uuid4())
tasks[task_id] = {"id": task_id, **data}
return JSONResponse(tasks[task_id], status_code=201)
app.run(host="0.0.0.0", port=8000, workers=4)
AI / LLM Routes
from ember import Ember, Request, SSEResponse, ConversationContext, sse_stream
import asyncio
app = Ember()
async def token_stream(prompt: str):
for word in f"You asked: {prompt}".split():
await asyncio.sleep(0.05)
yield word + " "
@app.ai_route("/v1/chat", methods=["POST"], streaming=True)
async def chat(request: Request, context: ConversationContext) -> SSEResponse:
body = await request.json()
prompt = body.get("message", "")
context.add_message("user", prompt)
return sse_stream(token_stream(prompt))
app.run(host="0.0.0.0", port=8000)
Caching
from ember import Ember, RedisCache, MemcachedCache, StaticCache
app = Ember()
# Static in-memory cache (no TTL, perfect for health checks)
@app.get("/health", cache=StaticCache())
async def health():
return {"status": "ok"}
# Redis cache — shared across all workers
task_cache = RedisCache(url="redis://localhost:6379", ttl=30)
@app.get("/tasks", cache=task_cache)
async def list_tasks(request):
...
# Memcached cache
@app.get("/posts", cache=MemcachedCache(host="localhost", port=11211, ttl=60))
async def list_posts(request):
...
Ember auto-connects on server start and auto-disconnects on stop — no lifecycle code needed.
Middleware
from ember import Ember, CORSMiddleware, BearerAuthMiddleware, APIKeyMiddleware
app = Ember()
app.add_middleware(CORSMiddleware(allow_origins=["https://myapp.com"]))
app.add_middleware(BearerAuthMiddleware(verify_fn=lambda token: token == "secret"))
app.add_middleware(APIKeyMiddleware(api_keys=["key-1", "key-2"], header="x-api-key"))
Blueprints
from ember import Ember, Blueprint, JSONResponse
app = Ember()
admin = Blueprint()
@admin.get("/users")
async def list_users():
return JSONResponse({"users": []})
@admin.post("/users")
async def create_user(request):
data = await request.json()
return JSONResponse(data, status_code=201)
app.add_blueprint(admin, prefixes={"*": "/admin"})
app.run(port=8000)
Limits & Rate Limiting
from ember import Ember, RouteLimits, TokenLimits, ServerLimits
app = Ember(
server_limits=ServerLimits(keep_alive_timeout=30),
)
@app.post(
"/upload",
limits=RouteLimits(max_body_size=50 * 1024 * 1024), # 50 MB
)
async def upload(request):
body = await request.body()
return {"size": len(body)}
@app.ai_route(
"/v1/chat",
methods=["POST"],
token_limits=TokenLimits(tokens_per_minute=10_000, max_prompt_tokens=4_096),
)
async def chat(request, context):
...
Project Layout
ember/
ai/ # ConversationContext, PromptTemplate, ToolRegistry, ModelRouter, SemanticCache
cache/ # StaticCache, RedisCache, MemcachedCache
headers/ # Cython headers parser
middleware/ # CORS, BearerAuth, APIKey
protocol/ # Cython llhttp HTTP/1.1 protocol
request/ # Cython Request + Stream
response/ # Cython Response, JSONResponse, SSEResponse …
router/ # Cython router with LRU cache, path params, regex
sessions/ # Session engine (in-memory, extensible)
workers/ # Fork worker, Reaper, graceful shutdown
application.py
server.py
examples/
basic_api.py
streaming_chat.py
tool_calling.py
tests/
setup.py
Running Tests
pip install "ember-api[dev]"
pytest
Contributing
See CONTRIBUTING.md.
Issues: bug reports · feature requests
License
MIT — see LICENSE.
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 ember_api-0.1.0.tar.gz.
File metadata
- Download URL: ember_api-0.1.0.tar.gz
- Upload date:
- Size: 1.1 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
42374b266b657394049eb2b8ef3ff1768674cc46c13574a36f031e65528545b1
|
|
| MD5 |
11bb9e4bbc1983e0e3447ffb508408b7
|
|
| BLAKE2b-256 |
8976f7e3c3e31f9bcf4e51a7f1a3b6b5f0a4a50a938794b7b4dc4598c24498a0
|
File details
Details for the file ember_api-0.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.
File metadata
- Download URL: ember_api-0.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
- Upload date:
- Size: 1.8 MB
- Tags: CPython 3.13, manylinux: glibc 2.17+ x86-64, manylinux: glibc 2.28+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
41d72edbbccd3dc14d6a01560d56c2381749ce55ca035d29af1fc2c114553c8a
|
|
| MD5 |
8cce5a7c72f96848a2c57e1f53d404fe
|
|
| BLAKE2b-256 |
f704cef35c30e4dc838882f652e1e852173e8b64d6d11aef0be9cb95d5ae3df1
|
File details
Details for the file ember_api-0.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl.
File metadata
- Download URL: ember_api-0.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
- Upload date:
- Size: 1.7 MB
- Tags: CPython 3.13, manylinux: glibc 2.17+ ARM64, manylinux: glibc 2.28+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6634565493839fd29c26dc820e1c451f05cfd4281fe94a8a2499ae64e5c3eb59
|
|
| MD5 |
2447c26168c83afac17d61a109186989
|
|
| BLAKE2b-256 |
51a03b81c154b43645864e5eb9cea1e1405d63808c3385daceffaa2c612eea87
|
File details
Details for the file ember_api-0.1.0-cp313-cp313-macosx_11_0_x86_64.whl.
File metadata
- Download URL: ember_api-0.1.0-cp313-cp313-macosx_11_0_x86_64.whl
- Upload date:
- Size: 1.4 MB
- Tags: CPython 3.13, macOS 11.0+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
15c8c136c8f098d9978fd3af6415b035fe0756fa3fd7b5fec3b64cd1993f71ae
|
|
| MD5 |
0d00e9c281c42e334416b827ec689620
|
|
| BLAKE2b-256 |
2265aa77a73c5145506b9ef619e1f7878d42a3ca8b7afc7e2c84bf63ca30ad45
|
File details
Details for the file ember_api-0.1.0-cp313-cp313-macosx_11_0_arm64.whl.
File metadata
- Download URL: ember_api-0.1.0-cp313-cp313-macosx_11_0_arm64.whl
- Upload date:
- Size: 1.4 MB
- Tags: CPython 3.13, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2a1e60e5d6106246f319026c90c0766625ec6842565ea69b7be40ee1412be49e
|
|
| MD5 |
7bede9896e06aacfa4eddb201276b0cb
|
|
| BLAKE2b-256 |
8e5482f151c659c8014fd8341259b80971168ece22f098a3ca11a19d9b4896c5
|
File details
Details for the file ember_api-0.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.
File metadata
- Download URL: ember_api-0.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
- Upload date:
- Size: 1.8 MB
- Tags: CPython 3.12, manylinux: glibc 2.17+ x86-64, manylinux: glibc 2.28+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6a4bae46ced0f34b34d3feb0a6fddf007e4ff600e6b20a01aaef2384c1b2afca
|
|
| MD5 |
d2e156949848dce10be1ba2625118f30
|
|
| BLAKE2b-256 |
d34a2468ad2dcdd5263435f104e30145e57a75a845756d854de090fd0d0e4d2b
|
File details
Details for the file ember_api-0.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl.
File metadata
- Download URL: ember_api-0.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
- Upload date:
- Size: 1.7 MB
- Tags: CPython 3.12, manylinux: glibc 2.17+ ARM64, manylinux: glibc 2.28+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5f8925a9966211b6d4c60495d92579f84525496f1d48001f8932550cd18316af
|
|
| MD5 |
6ef0fbcc02a03cf98478ece0e815bcc3
|
|
| BLAKE2b-256 |
67ffe4f8f5c75177b32eddffc66d65e6437435228f667c1d46b53bfddf479410
|
File details
Details for the file ember_api-0.1.0-cp312-cp312-macosx_11_0_x86_64.whl.
File metadata
- Download URL: ember_api-0.1.0-cp312-cp312-macosx_11_0_x86_64.whl
- Upload date:
- Size: 1.4 MB
- Tags: CPython 3.12, macOS 11.0+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fe095fb3f122e1f0f80460885b35780f6662ce407b9053048ef6c387afe79be0
|
|
| MD5 |
6241361b08ec22f10c601dd51b6e4659
|
|
| BLAKE2b-256 |
c95e74d90784311efe399d079f6e7ff5928408f3e0f25c8e4f9df00bc528ea14
|
File details
Details for the file ember_api-0.1.0-cp312-cp312-macosx_11_0_arm64.whl.
File metadata
- Download URL: ember_api-0.1.0-cp312-cp312-macosx_11_0_arm64.whl
- Upload date:
- Size: 1.3 MB
- Tags: CPython 3.12, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a2ec777627cc07956f98ae276b015c3e039bb0253b5b885d36cc36273dcbb94c
|
|
| MD5 |
245d7aaba0c2937d6dce1d047f3d153d
|
|
| BLAKE2b-256 |
781c68e1eab4fb7e45573e13dfb1a813ec5ec189c2cfa324846b1e56f524fa7e
|