Skip to main content

IKAREM

Zero-dependency Python ASGI framework, built for humans and LLMs.

IKAREM logo — a reversed K slashed through, over mirrored MERAKI, also slashed

CI PyPI Python License

FastAPI-style DX (DI, validation, OpenAPI, auth) + Django/Nest-style structure (plugins, config, RBAC) + a core that runs on stdlib alone. Pip-installable, zero required dependencies.

A Python web framework for REST APIs, HTML apps, websockets, background jobs, and LLM tool servers — one package from prototype to production.

Install

pip install -e .                  # core only, zero dependencies
pip install -e ".[server,test]"   # uvicorn + pytest + httpx for dev
pip install -e ".[postgres]"      # asyncpg strategy
pip install -e ".[mysql]"         # aiomysql strategy
pip install -e ".[sqlserver]"     # aioodbc strategy

30-second example

from ikarem import Ikarem

app = Ikarem(debug=True)


@app.get("/")
async def home(req):
    return {"hello": "ikarem"}  # dict auto-becomes JSON


@app.get("/users/{uid:int}")
async def get_user(req, uid: int):
    return {"uid": uid}


if __name__ == "__main__":
    app.run()  # needs `pip install ikarem[server]`
python examples/basic.py
# or
ikarem run examples.basic:app --reload

Industry-grade example

from ikarem import (
    BackgroundTasks,
    Depends,
    Ikarem,
    Schema,
    CORSMiddleware,
    RateLimitMiddleware,
    create_token,
    require_roles,
)

app = Ikarem(auth_secret="secret")
app.use(CORSMiddleware())
app.use(RateLimitMiddleware(per_minute=120))


class Item(Schema):
    name: str
    qty: int = 1


def get_prefix():
    return "hi"


@app.post("/items")
async def create(req, item: Item, bg: BackgroundTasks, prefix=Depends(get_prefix)):
    bg.add(print, f"created {item.name}")
    return {"msg": f"{prefix} {item.name}", "qty": item.qty}  # validated + coerced


tok = create_token("u1", "secret", roles=["admin"])


@app.get("/admin")
async def adm(req, claims=Depends(require_roles("admin"))):
    return {"sub": claims["sub"]}  # 401 without token, 403 without role

Built-ins on every app: GET /healthz, GET /metrics, GET /openapi.json (OpenAPI 3.1 auto-gen), GET /docs (Swagger UI).

Production web apps

ikarem new myapp && cd myapp   # auth + sessions + SQLite CRUD (HTML + JSON), tests + Dockerfile
pytest -q && uvicorn app:app
from ikarem import SessionMiddleware, CSRFMiddleware, Field, Schema

app.use(SessionMiddleware())  # signed cookies: req.session["uid"] = ...
app.use(CSRFMiddleware())  # unsafe routes need X-CSRF-Token or _csrf_token field


class Signup(Schema):
    email: str = Field(..., email=True, max_length=254)
    password: str = Field(..., min_length=8, max_length=128)


class Note(Schema):
    text: str = Field(..., min_length=1, max_length=500)


@app.post("/notes")
async def add(req, note: Note):  # validates JSON *and* HTML form bodies
    form = await req.form()  # urlencoded + multipart, UploadFile files,
    f = form.get("doc")  # 413 past size caps
    await f.write(f"/uploads/{f.filename}")

TestClient keeps a cookie jar (login flows just work) and speaks get/post/put/patch/delete. ikarem check audits handlers, ikarem mcp serves every route as an LLM tool (plus ikarem://openapi.json and ikarem://manifest MCP resources), ikarem inspect prints a compact route manifest for LLM context, and site/llms.txt is the framework manual in one page. AGENTS.md holds the contributor laws for AI and human agents alike.

Flask's best, taken

from ikarem import Blueprint, MethodView, Templates, abort, flash

api = Blueprint("api", url_prefix="/api")


@api.get("/items/{uid:int}")  # own hooks, own errors, url_for("api.x")
async def one(req, uid: int): ...


app.register_blueprint(api)  # hooks wrap once — still zero per-request reflection

abort(403, "owner only")  # terse errors through the normal pipeline


class Items(MethodView):  # one class per resource, full DI per method
    async def get(self, req): ...
    async def post(self, req, item: Item): ...


app.route("/items", Items.methods())(Items.as_view("items"))

flash(req, "Saved.")  # session-backed, shown once in templates
Templates("templates/").response("hi.html", name="amy")  # Jinja2 via ikarem[jinja]

Deliberately not taken: context locals (g, global request proxies) and signals — explicit req params plus plugins and middleware cover that ground without the magic.

Ops batch: migrations, queues, cron, API keys

from ikarem import Migrator, QueuePlugin, require_scopes, APIKeyAuth, task
from ikarem.db import DatabasePlugin

app.register(DatabasePlugin("postgresql://..."))
app.register(QueuePlugin())  # durable jobs in app.state_queue


@task("welcome")  # name -> callable, worker-dispatched
async def welcome(to: str): ...


@app.post("/signup")
async def signup(req):
    await req.app.state_queue.enqueue("welcome", {"to": "a@b.co"})
    return {"ok": True}, 201


@app.every(300)  # or @app.cron("0 2 * * *")
async def nightly(): ...


# await app.start_scheduler()                  # explicit: nothing runs unless started
ikarem migrate new add_users && ikarem migrate up myapp:app
ikarem worker myapp:app                        # drain the queue until Ctrl+C

Plus the small ones the checklist demanded: Schema(extra="forbid") sanitization, XMLResponse/dict_to_xml, escape_html, APIKeyAuth (static keys or async lookup=), require_scopes() for JWT scopes, require_if() predicate (ABAC-lite). Out of scope on purpose: full ORM (strategy + validated schemas is the answer) and OAuth2 dance (JWT bearer covers service auth).

Stronger: timeouts, bulkheads, idempotency, rooms

from ikarem import TimeoutMiddleware, ConcurrencyLimitMiddleware, IdempotencyMiddleware, TrustedHostMiddleware

app.use(TimeoutMiddleware(30))  # hung handler -> 503 + Retry-After
app.use(ConcurrencyLimitMiddleware(100))  # bulkhead: fail fast past N in-flight
app.use(IdempotencyMiddleware())  # Idempotency-Key replays, no double charges
app.use(TrustedHostMiddleware(["example.com", ".example.com"]))

Plus Ikarem(max_body_bytes=...) DoS floor, latency fields in /metrics, atomic SQLite transactions, Room pub/sub for websockets (tested via TestClient.ws_connect), and a py.typed marker so downstream type checkers see the real types.

NISH Mode: one switch

from ikarem import Ikarem
from ikarem.testing import TestClient

app = Ikarem(enable_docs=False)
app.nish_mode()  # or app.nish = True


@app.get("/hello")
async def hello(req):
    return {"hello": "ikarem"}


c = TestClient(app)
assert c.get("/hello").json() == {"hello": "ikarem"}
nish = c.get("/hello", query="format=nish")
assert nish.body.startswith(b"NISH/1.0") and "etag" in nish.headers

One switch and the whole API negotiates NISH (?format=nish or Accept), both shapes get content-hash ETags with automatic 304s, errors speak NISH, await req.nish() parses bodies, /openapi.nish serves the contract, and app.nish_mode(config="app.nish") loads typed config. Full story: docs/NISH.md.

Live proof it all works, three apps deep: ledger/ — a personal-finance app (auth, dashboard with SVG charts, CRUD, receipt uploads, CSV export, JSON API); cadence/ — a habit tracker (streaks, heatmaps); forge/ — a workshop OS (jobs, kanban, crew chat, ledger, habits, NISH-first API). All running on stock IKAREM

  • uvicorn.

Switching? Bring your routes

Meraki (5 minutes):

-from meraki import Meraki
+from ikarem.meraki_compat import Meraki

Your app runs unchanged (same routes, middleware, 404/405 bodies) on the IKAREM engine — then migrate handler-by-handler. From anywhere else:

Plus honest benchmarks: bench/RESULTS.md · extension registry: docs/ECOSYSTEM.md · 20 runnable recipes: docs/COOKBOOK.md · From Zero to Production guide: docs/GUIDE.md (PDF) · NISH responses for the Viewer extension: docs/NISH.md · install: pip install ikarem.

Footnote: IKAREM started as an answer to Meraki — same decorator shape, working plugins, and everything Meraki's README promised but never shipped. The rivalry is archived; this section is just the moving van.

Why IKAREM

Rival feature IKAREM answer
pip package, minimal deps Zero required deps. uvicorn/asyncpg/etc are optional extras. Core is pure stdlib + ASGI.
ASGI + Uvicorn boundary Strict server boundary: Ikarem exposes __call__(scope, receive, send). Any ASGI server works (uvicorn, hypercorn, daphne). HTTP + WebSocket + lifespan.
Central app + lifecycle Ikarem() + on_startup / on_shutdown + lifespan. Plugins hook in with priority + topological dependency order.
Request/Response Lazy Request (query, headers, cookies, await body()/json()), Response helpers (JSON, text, html, stream, redirect, File). Handlers never touch raw ASGI.
Routing Compiled routes with {param} + {param:int/float/uuid/path} converters, 404 vs 405 distinction, url_for, include_router(prefix), static mounts.
Middleware Onion pipeline, short-circuitable, global + composable: CORS, security headers, rate limiting (429 + Retry-After), request-ID/timing, metrics.
Plugins Plugin protocol (name, requires, priority, register, on_startup/shutdown/request/response). Manager sorts dependencies, detects cycles.
Config Layered Config: defaults < kwargs < dict < IKAREM_* env vars. Typed config.get(key, default, cast=...).
Errors HTTPException hierarchy + @app.exception_handler(ExcType) with MRO most-specific match. Tracebacks only when debug=True.
DB Strategy (pg/mysql/sqlite/mssql) DatabaseConnector async ABC (connect/disconnect/execute/fetch_one/fetch_all/execute_many/transaction). Lazy driver imports. SQLite runs on stdlib today. DatabasePlugin proves the extension model.
DI (FastAPI parity) Depends() with nesting, per-request cache (+ opt-out), sync/async/yield deps, finalizers guaranteed after response send and on the exception path, circular-dep rejection.
Validation (Pydantic-lite) Schema models from type hints: coercion, required/optional, nested models, ValidationError → 400. Zero deps.
Auth Stdlib HS256 JWT (algorithm-confusion resistant, sub required, expiry enforced), pbkdf2 passwords, BearerAuth + APIKeyAuth, require_roles() / require_scopes() / require_if().
Caching MemoryCache + RedisCache + @cached over a swappable CacheBackend interface.
Background work BackgroundTasks param (after send) + durable Queue (survives deploys) + cron.
Realtime / files app.websocket(path) + Room pub/sub + WebSocket helper; mount_static() + FileResponse.
NISH app.nish_mode(): whole-API negotiation, ETags + 304s, req.nish(), /openapi.nish, NISH config.
Observability JSON logging, x-request-id + x-process-time-ms, /healthz + /readyz + /metrics.

Verification

278 passed, 3 skipped — framework plus three showcase apps, one command:

python -m pytest tests/ ledger/tests cadence/tests forge/tests -q

CI runs the same suite on Python 3.10–3.13 × Ubuntu/macOS/Windows, plus a live-Postgres job, a Docker showcase build, a starter-template smoke job (ikarem new + template tests), ruff lint + format, and ikarem check ledger.app:app. Every behavior ships with a test; bugfix PRs include a regression test. check also AST-audits handler source — f-string/.format()/% SQL and blocking calls warn with remedies (the interpreter accepts both; production regrets both). Gate harder with check --strict, emit machine output with check --format json, review exposure with inspect --format auth, dump the spec with inspect --format openapi, and list tools with mcp --list.

Layout

ikarem/            zero-dep stdlib core (v1.2.0) — optional integrations lazy-load behind extras
  app.py          Ikarem core + ASGI callable + lifespan + background/cleanup wiring
  compiled.py     one-time handler plans (no per-request reflection) + check/describe IR
  routing.py      compiled routes + converters + Did-you-mean 404s
  http.py         Request (+forms/uploads) + Response family (+cookies)
  di.py           Depends + nesting + cycle detection + run_cleanups
  validation.py   Schema models + Field() constraints (zero-dep validation)
  auth.py         JWT + passwords + Bearer/API-key auth + roles/scopes guards
  session.py      signed-cookie sessions + CSRF
  security.py     CORS + security headers + trusted hosts + rate limiting
  resilience.py   timeouts + bulkheads + idempotency
  cache.py        CacheBackend + MemoryCache + RedisCache + @cached
  nish.py         NISH writer/reader + NISHResponse + negotiate (see NISH Mode)
  conditional.py  ConditionalMiddleware (ETag 304s)
  db/             DatabaseConnector ABC + sqlite/postgres/mysql/sqlserver + plugin + factory
  queue.py        durable task queue + QueuePlugin + `ikarem worker`
  scheduler.py    cron/intervals + SchedulerPlugin
  migrations.py   versioned migrations + `ikarem migrate`
  resources.py    app.resource() validated CRUD
  blueprints.py   prefixed route groups + MethodView (views.py)
  middleware.py   Middleware base + stack
  plugins.py      Plugin protocol + dependency-sorted PluginManager
  config.py       layered Config + NISH config files
  errors.py       HTTPException hierarchy + handler registry
  openapi.py      OpenAPI 3.1 builder + /openapi.json + /openapi.nish + /docs
  mcp.py          routes-as-MCP-tools + resources + stdio server
  websocket.py    WebSocket disconnects + Room pub/sub + WSRouter
  static.py       FileResponse + escape-proof static mounts
  templating.py   Jinja2 via ikarem[jinja] + flashing.py one-shot messages
  observability.py  logging + request-ID + metrics + /healthz + /readyz + /metrics
  cli.py          `ikarem run|check|mcp|new|migrate|worker|inspect` (+ audit.py source checks)
  testing.py      TestClient (cookie jar, all verbs, WS driving — no server needed)
  scaffold.py     `ikarem new` starter generator
  deprecation.py  deprecated() upgrade path + meraki_compat.py drop-in shim
tests/ + ledger/tests + cadence/tests + forge/tests   278-test suite (see Verification)
ledger/ + cadence/ + forge/   production showcase apps (finance, habits, workshop OS)
examples/basic.py    minimal CRUD + DB plugin app
bench/            honest benches (bench_switch.py) + sustained-load proof (load.py)
docs/             COOKBOOK.md (20 runnable recipes) · GUIDE.md (25 chapters) · NISH.md ·
                  ECOSYSTEM.md (extension registry) · MIGRATING_FROM_*.md (6 frameworks) ·
                  DEPLOY.md · PLUGINS.md · SECURITY.md · PHASE1.md (original spec)
site/             static docs site (no build step) + llms.txt framework manual

Project basics

  • Security: SECURITY.md — supported versions, how to report (GitHub private vulnerability reporting; no public issues for vulns).
  • Contributing: CONTRIBUTING.md — setup, per-PR checks, the five agent laws (zero-dep core, tests, no per-request reflection, fix-saying errors, Conventional Commits).
  • Changelog: CHANGELOG.md — every release cut from Conventional Commits, Keep-a-Changelog format.
  • Versioning (SemVer): 1.x keeps the public API backward-compatible — ikarem/__init__.py exports, ASGI behavior, CLI commands, response shapes. Minor versions add; breaking changes wait for a major.
  • Deprecation policy: removals land only in majors, announced at least one minor earlier via deprecated(since=, removal=, use_instead=) — upgrades warn with the version, the removal target, and the replacement.
  • CI: Python 3.10–3.13 × Ubuntu/macOS/Windows, live Postgres, Docker build, scaffold smoke, ruff lint + format.

Metadata

Release files for ikarem 1.2.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 ikarem 1.2.0
File Size Uploaded
ikarem-1.2.0.tar.gz 124.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for ikarem 1.2.0
File Interpreter ABI Platform
ikarem-1.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 229.6 kB

Release files / ikarem-1.2.0.tar.gz

Download URL ikarem-1.2.0.tar.gz
Size 124.8 kB
Tags Source
SHA-256 checksum
How to use checksums
6b1f9b17c60693d26d3d1d3015584cfe8cfb3de571648525c7c07f9d4042ef63
BLAKE2b-256 checksum
How to use checksums
d9dd91d2df8e3a16a1d1ee5b97ac448bec8ba11bd0569b7f0b4f20f435d015f8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 2, 2026.

Transparency log

Release files / ikarem-1.2.0-py3-none-any.whl

Download URL ikarem-1.2.0-py3-none-any.whl
Size 104.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f36abe532172fbc424f5b8e28ebc4732819c2ef70c76ec06dc47e1bf68d5d6ab
BLAKE2b-256 checksum
How to use checksums
41f6a5051758ef8e2ee857444302926b7580af3032d3dbe9cd92b777f27da7b6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 2, 2026.

Transparency log

Release history Release notifications | RSS feed

1.4.0

2 release files

1.3.0

2 release files

This release

1.2.0 This release

2 release files

1.0.0

2 release files

0.3.0

2 release files

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