Skip to main content

Fenrir Logo

Fenrir Web Framework

PyPI - Version Python Versions License: MIT Tests codecov CodSpeed Security Ruff Mypy Docker

Fenrir is a state-of-the-art, high-performance, hybrid Python web framework built on top of modern ASGI specifications. It elegantly merges the best programming paradigms from Python's most popular web frameworks (Flask, FastAPI, Sanic, Falcon, and Bottle) into a single unified workspace, powered locally by the premium Asteri application server.

Whether you prefer the automatic Pydantic validation of FastAPI, the seamless context-locals of Flask, the raw class-based speed of Falcon, or the robust background task model of Sanic, Fenrir allows you to leverage them all simultaneously in the same codebase.


📦 Installation

Install directly from PyPI:

pip install fenrir-framework

Optional extras:

pip install fenrir-framework[redis]      # Redis sessions & rate limiting
pip install fenrir-framework[orm]        # SQLite + PostgreSQL ORM (aiosqlite, asyncpg)
pip install fenrir-framework[graphql]    # Strawberry GraphQL support
pip install fenrir-framework[grpc]       # gRPC server/client support
pip install fenrir-framework[testing]    # HTTPX-based TestClient / fenrir bench
pip install fenrir-framework[all]        # everything above

Or install in development mode by cloning the repository:

git clone https://github.com/IshikawaUta/fenrir.git
cd fenrir
pip install -e ".[all]"

🌟 Key Features

  • ⚡ High-Speed ASGI Core: Extremely low-overhead routing and handler pipeline, achieving massive request throughput.
  • 🔺 Trie-Based Routing: O(k) route matching where k = path depth, instead of O(n) linear scan. Handles 1000+ routes efficiently.
  • 🧩 Framework Hybridization:
    • FastAPI Paradigm: Native Pydantic v2 data validation, Annotated type decorators, automated parameter resolution (Query, Path, Header, Cookie, Body), dynamic dependency injection (Depends), and automated response_model serialization.
    • Flask Paradigm: Thread/Task-safe context locals (request, g, session), Jinja2 template rendering (render_template), and request teardown hooks.
    • Falcon Paradigm: Class-based resource controllers (on_get, on_post), before/after hooks, and in-place response mutation.
    • Sanic Paradigm: Global sys.modules patching (install_sanic_compat()), standard response helpers (json, text, html, raw, redirect), lifecycle listeners (before_server_start, etc.), and a background event scheduler (app.add_task).
    • Bottle Paradigm: Built-in WSGI-to-ASGI wrapper and legacy mount adapter (app.mount_wsgi()) to run old WSGI applications at ASGI speeds.
  • 📖 Auto-Generated OpenAPI Docs: Interactive Swagger UI (/docs) and ReDoc (/redoc) instantly generated from your Pydantic schemas and route metadata.
  • 🔌 Modern Communications: Out-of-the-box support for WebSockets (with authentication) and Server-Sent Events (SSE).
  • 🔐 WebSocket Authentication: WebSocketTokenAuth dependency for token-based WebSocket authentication via headers or query parameters.
  • 🗄️ Connection Pooling: Built-in generic ConnectionPool and DatabasePool with health checks, retry logic, and automatic connection recycling.
  • 🌐 HTTP/2 Push: HTTP2Push utility for server push with Link headers and auto-push decorators.
  • ⏱️ Advanced Rate Limiting: Per-IP or per-user rate limiting with optional Redis backend for distributed deployments.
  • 🛡️ Body Size Limits: BodyLimitMiddleware to reject oversized request bodies and prevent DoS attacks.
  • 🔒 CSRF Protection: CSRFMiddleware for cross-site request forgery token validation on state-changing methods, with automatic token generation and cookie injection.
  • 📦 Streaming Request Body: stream_body() method for memory-efficient processing of large uploads without buffering.
  • 🗜️ Streaming GZip Compression: GZipMiddleware compresses each chunk on-the-fly for StreamingResponse, with default compression level 6 (optimal CPU/ratio trade-off).
  • ⚡ Signature & Schema Caching: inspect.signature() and OpenAPI schema are cached for faster repeated requests.
  • 🛠️ Premium CLI Tooling: Visual route tables, interactive app shell, in-memory benchmarking suite, project scaffolding, and environment system inspection.
  • 📊 Built-in Monitoring Dashboard: Health checks, traffic analysis, error rates, alerts, uptime stats, response time history, and hourly traffic with secure bcrypt authentication.
  • 🔌 Plugin System: Version compatibility, dependency resolution, config validation, hot-reload, auto-discovery, health monitoring.
  • 🪝 Hook/Extension Points: Priority ordering, one-time hooks, wildcard hooks, async/sync support, middleware integration.
  • 🗄️ Lightweight ORM: SQLite/PostgreSQL support, Model with metaclass, QuerySet with filters/ordering, SQL injection prevention.
  • 💾 Caching System: MemoryCache (LRU + TTL), RedisCache (SCAN not KEYS), FileCache (atomic writes).
  • 📋 Queue/Job System: Job with retry/backoff/priority/timeout, Worker with concurrency, MemoryQueue and RedisQueue backends.
  • 🔗 GraphQL Support: strawberry-graphql integration with GraphiQL playground.
  • 📡 gRPC Support: GRPCServer, GRPCService, GRPCClient, interceptors.
  • 📊 Performance Module: ObjectPool, ResponseCache, PerformanceMonitor, optimize_app().
  • ⚡ orjson Integration: All JSON serialization uses orjson (7x faster than stdlib json).
  • 🐍 Python 3.8–3.13 Compatible: Full backward compatibility ensured via typing_extensions polyfills for Annotated, get_origin, get_args; and a contextvars-aware asyncio.to_thread shim.

🚀 Quick Start (The Hybrid Power)

Here is a simple example (demo_app.py) showcasing how Flask, FastAPI, Falcon, and Sanic styles coexist harmoniously in a single application with built-in monitoring:

import os
import logging
from pydantic import BaseModel
from fenrir import (
    Fenrir, Blueprint, request, g, Depends, Query, Header,
    render_template, Response, Form, File, UploadFile,
    WebSocket, WebSocketDisconnect
)
from fenrir.features import init_fenrir_monitoring

# Load environment variables from .env file
try:
    from dotenv import load_dotenv
    load_dotenv()
except ImportError:
    pass

# Setup logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("demo")

# Initialize App
app = Fenrir(title="Fenrir Hybrid Framework Demo", version="4.2.0")

# --- Enable Built-in Features ---
# Monitoring Dashboard: /monitoring (login: admin/changeme)
# Configure via .env or CLI: fenrir monitoring enable
init_fenrir_monitoring(app)

# --- 1. FastAPI-style Pydantic Validation & Dependency Injection ---
class UserRegister(BaseModel):
    username: str
    email: str
    age: int

async def verify_api_key(x_api_key: str = Header(default=None)):
    expected_key = os.getenv("API_KEY", "changeme-set-API_KEY-env")
    if x_api_key != expected_key:
        logger.warning("Invalid API key provided!")
    return x_api_key

# --- 2. Flask-style Decorators, Context-Locals, and Templating ---
@app.get("/")
async def home():
    name = request.args.get("name", "Fenrir User")
    return render_template("index.html", name=name)

# Form & File Upload Endpoint
@app.post("/upload")
async def handle_upload(
    title: str = Form(),
    file: UploadFile = File()
):
    content = await file.read()
    return {
        "title": title,
        "filename": file.filename,
        "content_type": file.content_type,
        "size": len(content)
    }

# WebSocket Echo Endpoint
@app.websocket("/ws/chat")
async def chat_ws(ws: WebSocket):
    await ws.accept()
    try:
        while True:
            msg = await ws.receive_text()
            await ws.send_text(f"Fenrir Chat Echo: {msg}")
    except WebSocketDisconnect:
        logger.info("Chat WebSocket disconnected")

# --- 3. Falcon-style Class-based Resources ---
class ItemResource:
    async def on_get(self, req, resp, item_id: int):
        resp.status = 200
        resp.media = {
            "item_id": item_id,
            "status": "active",
            "msg": f"Fetched item {item_id} (Falcon Resource style)"
        }

    async def on_post(self, req, resp, item_id: int):
        data = req.json
        resp.status = 201
        resp.media = {
            "item_id": item_id,
            "received_body": data,
            "msg": f"Created sub-item for item {item_id} (Falcon Resource style)"
        }

app.add_route("/items/<item_id:int>", ItemResource())

# --- 4. Sanic-style Listeners and Middlewares ---
@app.listener("before_server_start")
async def setup_db(app_instance):
    logger.info("[Listener] Initializing mock database connection pool...")
    app_instance.db_pool = "Connected"

@app.listener("after_server_stop")
async def teardown_db(app_instance):
    logger.info("[Listener] Closing database connection pool...")

@app.middleware("request")
async def log_request_path(req):
    logger.info(f"[Middleware] Request received: {req.method} {req.path}")
    g.user_type = "guest"

@app.middleware("response")
async def add_custom_powered_by(req, resp):
    logger.info(f"[Middleware] Response sent: {resp.status}")
    resp.headers["X-Powered-By"] = "Fenrir Framework"

# --- 5. Flask/Sanic-style Blueprint modular routing ---
api_bp = Blueprint("api", url_prefix="/api")

@api_bp.post("/register")
async def register_user(
    body: UserRegister,
    api_key: str = Depends(verify_api_key),
    role: str = Query(default="member")
):
    return {
        "status": "success",
        "user_type": g.user_type,
        "role": role,
        "api_key_used": api_key,
        "registered_user": body.model_dump()
    }

app.register_blueprint(api_bp)

# --- 6. Custom Exception Handler ---
@app.exception(ValueError)
async def handle_value_error(req, exc):
    return Response(f"Custom Value Error: {exc}", status=400)

@app.get("/trigger-error")
async def trigger_error():
    raise ValueError("Something went wrong!")

# --- 7. Run with Asteri ASGI Server ---
if __name__ == "__main__":
    app.run(host="127.0.0.1", port=8000, workers=2, app_path="demo_app:app")

🔺 Trie-Based Routing

Fenrir v4.2.0 uses a trie-based routing index for O(k) route matching, where k is the path depth. This is significantly faster than linear O(n) matching when you have many routes.

from fenrir import Fenrir

app = Fenrir()

# These routes are indexed in a trie for fast lookup
@app.get("/api/v1/users")
async def list_users(): ...

@app.get("/api/v1/users/<int:user_id>")
async def get_user(user_id: int): ...

@app.get("/api/v1/posts/<int:post_id>/comments")
async def get_comments(post_id: int): ...

# Route matching is O(k) where k = number of path segments
# /api/v1/users/42 → checks: api → v1 → users → 42 (parametric)

🔐 WebSocket Authentication

Authenticate WebSocket connections using tokens from headers or query parameters:

from fenrir import Fenrir, WebSocket, Depends
from fenrir.security import WebSocketTokenAuth

app = Fenrir()
auth = WebSocketTokenAuth()

@app.websocket("/ws")
async def websocket_handler(websocket: WebSocket, token: str = Depends(auth)):
    await websocket.accept()
    await websocket.send_text(f"Authenticated with token: {token}")
    while True:
        data = await websocket.receive_text()
        await websocket.send_text(f"Echo: {data}")

🗄️ Connection Pooling

Built-in connection pooling for databases and external services:

from fenrir import Fenrir
from fenrir.pool import ConnectionPool

app = Fenrir()

# Create a connection pool
pool = ConnectionPool(
    create_func=lambda: create_engine("sqlite:///db.sqlite3"),
    close_func=lambda engine: engine.dispose(),
    min_size=2,
    max_size=10,
)

@app.get("/users")
async def list_users():
    async with pool.acquire() as conn:
        result = conn.execute("SELECT * FROM users")
        return {"users": [dict(row) for row in result]}

🌐 HTTP/2 Push

Proactively push resources to clients before they request them:

from fenrir import Fenrir
from fenrir.http2 import HTTP2Push

app = Fenrir()
push = HTTP2Push()

@app.get("/")
async def index():
    return push.push(
        "<html><link rel='stylesheet' href='/static/style.css'></html>",
        push_paths=["/static/style.css", "/static/app.js"],
    )

⏱️ Advanced Rate Limiting

Per-IP or per-user rate limiting with optional Redis backend:

from fenrir import Fenrir
from fenrir.middleware import RateLimitMiddleware

app = Fenrir()

# Per-IP rate limiting
app.add_middleware(RateLimitMiddleware, max_requests=100, window_seconds=60)

# Per-user rate limiting
def user_key(scope):
    for k, v in scope.get("headers", []):
        if k == b"x-user-id":
            return v.decode("latin-1")
    client = scope.get("client")
    return client[0] if client else "unknown"

app.add_middleware(RateLimitMiddleware, key_func=user_key)

# Distributed rate limiting with Redis
import redis.asyncio as aioredis
redis_client = aioredis.Redis()
app.add_middleware(RateLimitMiddleware, redis_client=redis_client)

📦 Streaming Request Body

Process large uploads efficiently without buffering the entire body:

from fenrir import Fenrir, Request

app = Fenrir()

@app.post("/upload")
async def upload(request: Request):
    total_bytes = 0
    async for chunk in request.stream_body(chunk_size=65536):
        total_bytes += len(chunk)
        # Process each chunk without loading entire body into memory
    return {"bytes_received": total_bytes}

🗄️ ORM

A lightweight async ORM for SQLite/PostgreSQL (aiosqlite/asyncpg) with metaclass-based models, chained query filtering, ordering, and SQL-injection-safe queries.

from fenrir import Fenrir
from fenrir.orm import Database, Integer, Model, String

app = Fenrir()

class User(Model):
    __tablename__ = "users"
    id = Integer(primary_key=True)
    name = String(max_length=100)
    email = String(max_length=255, unique=True)

db = Database("sqlite:///app.db")   # or "postgresql://user:pass@host/db"
User.bind(db)
await db.create_all()

@app.get("/users/<int:user_id>")
async def get_user(user_id: int):
    user = await User.get(id=user_id)
    return {"user": user.to_dict() if user else None}

@app.get("/users")
async def list_users():
    return {"users": [u.to_dict() for u in await User.all()]}

Classmethods on models: create, get, get_or_create, all, filter, count, bulk_create; filter()/exclude()/order_by()/limit()/offset() chain into async terminators all(), first(), count(), delete(), update(**kwargs). Models support auto_now/auto_now_add datetime fields, JSON fields, save()/delete()/to_dict() on instances, and transactions via async with db.transaction():.


💾 Caching

Built-in caching with pluggable backends — MemoryCache (LRU + TTL), RedisCache (uses SCAN, not KEYS), and FileCache (atomic writes).

from fenrir import Fenrir
from fenrir.cache import Cache, MemoryCache

app = Fenrir()
cache = Cache(backend=MemoryCache(max_size=1000))

@app.get("/cached")
async def cached():
    value = await cache.get("key")
    if value is None:
        value = await compute_expensive_thing()
        await cache.set("key", value, ttl=60)
    return {"value": value}

📋 Queues & Jobs

Background job queue with retry/backoff/priority/timeout and a worker pool, backed by MemoryQueue or RedisQueue.

from fenrir import Fenrir
from fenrir.queue import Queue, Worker

app = Fenrir()
queue = Queue()

@queue.handler("send_email")
async def send_email(email: str):
    # send the email ...
    return {"sent_to": email}

@app.get("/enqueue")
async def enqueue():
    job = await queue.enqueue("send_email", email="a@b.com", max_retries=3)
    return {"job_id": job.id}

Worker(queue).start()   # processes jobs concurrently

Register job functions with the @queue.handler("name") decorator. Jobs support retry with exponential backoff, priority ordering, timeouts, delays, and cancellation.


🔐 Sessions

Flask-style session context-local backed by InMemorySessionInterface (default) or RedisSessionInterface for distributed deployments.

import redis.asyncio as aioredis
from fenrir import Fenrir, session
from fenrir.sessions import RedisSessionInterface

app = Fenrir()
app.config["SECRET_KEY"] = "change-me"                 # strong random value in prod
app.session_interface = RedisSessionInterface(
    redis_client=aioredis.from_url("redis://localhost:6379"),
    prefix="fenrir:",
)

@app.get("/login")
async def login():
    session["user"] = {"id": 1}
    return {"ok": True}

SESSION_COOKIE_SECURE, SESSION_COOKIE_HTTPONLY, SESSION_COOKIE_SAMESITE, and SESSION_COOKIE_NAME are configured via app.config[...] (secure cookies are enabled by default).


📢 Signals

A Flask-like signal system with named signals, sender filtering, and sync/async receivers.

from fenrir import Fenrir
from fenrir.signals import Namespace

app = Fenrir()
ns = Namespace()
user_created = ns.signal("user-created")

@user_created.connect
async def on_user_created(sender, **kwargs):
    print("New user:", kwargs["user"])

user_created.send(sender=app, user={"id": 1})   # sync; receivers may be async

📑 Pagination

paginate() wraps any sequence into a standardized paginated envelope with total, page, size, pages, has_next/has_prev, and link URLs.

from fenrir import Depends
from fenrir.pagination import PaginationParams, paginate

@app.get("/items")
async def items(page: PaginationParams = Depends(PaginationParams)):
    rows = await fetch_all_rows()          # your query
    return paginate(rows, page=page.page, size=page.size, base_url="/items")

🧰 Helpers

URL generation and file responses:

from fenrir import Fenrir, redirect, send_file, send_from_directory, url_for

app = Fenrir()

@app.get("/users/<int:uid>")
async def profile(uid: int):
    return {"profile": uid}

@app.get("/go")
async def go():
    return redirect(url_for("profile", uid=42))       # → /users/42

@app.get("/download")
async def download():
    return send_file("reports.pdf", as_attachment=True, download_name="report.pdf")

@app.get("/static/<path:path>")
async def static_files(path: str):
    return send_from_directory("public", path)

🚦 HTTP Exceptions

Rich HTTP exception hierarchy that can be raised or returned; each carries an HTTP status code and description.

from fenrir import Fenrir, HTTPBadRequest, HTTPForbidden, HTTPNotFound

@app.get("/secure/<int:uid>")
async def secure(uid: int):
    if uid != 1:
        raise HTTPForbidden("You cannot access this resource.")
    return {"uid": uid}

@app.get("/missing")
async def missing():
    raise HTTPNotFound("This page does not exist.")

Catch exceptions with @app.exception(...) to return custom responses.


🗂️ Class-Based Views

View and MethodView base classes for organizing handlers by HTTP method.

from fenrir import Fenrir, request
from fenrir.views import MethodView

app = Fenrir()

class UserView(MethodView):
    async def get(self, user_id: int):
        return {"method": "GET", "user_id": user_id}
    async def post(self):
        return {"method": "POST", "body": request.json}

app.add_route("/users/<int:user_id>", UserView())

📦 Response Classes

Explicit response types for streaming, files, redirects, and raw bodies:

from fenrir import (
    Fenrir, FileResponse, HTMLResponse, JSONResponse, PlainTextResponse,
    RedirectResponse, StreamingResponse,
)

app = Fenrir()

@app.get("/json")
async def json_route():
    return JSONResponse({"ok": True})

@app.get("/html")
async def html_route():
    return HTMLResponse("<h1>Hello</h1>")

@app.get("/text")
async def text_route():
    return PlainTextResponse("plain text")

@app.get("/stream")
async def stream_route():
    return StreamingResponse(iter(b"chunk"))

@app.get("/old")
async def old_route():
    return RedirectResponse("/new", status_code=301)

🧪 Testing

Use the built-in TestClient (HTTPX-based) for in-process ASGI testing:

import pytest
from myapp import app   # your Fenrir app

@pytest.mark.anyio
async def test_home():
    async with app.test_client() as client:
        r = await client.get("/")
        assert r.status_code == 200

TestClient/FenrirTestClient are available as fenrir.TestClient and from fenrir.testing import FenrirTestClient. Requires the testing extra (pip install fenrir-framework[testing]).


🔧 Environment Variables

Fenrir reads a .env file via python-dotenv. Core variables:

Variable Purpose
SECRET_KEY Secret key used to sign sessions/CSRF tokens. Set a strong random value in production.
MONITORING_ENABLED Enable (true/false) the built-in monitoring dashboard.
MONITORING_USER Monitoring dashboard username (default admin).
MONITORING_PASSWORD Monitoring dashboard password (default changeme).
MONITORING_SECRET_KEY Secret key for monitoring dashboard auth.
MONITORING_SITES Comma-separated URLs for the monitoring health checks.

See .env.example for the full template.


💻 CLI Command Reference

Fenrir comes packed with a high-fidelity, visually rich command-line tool. Start the CLI by executing fenrir or python -m fenrir.cli.

1. fenrir run

Serve your application locally. Powered by Asteri, supporting dynamic multiprocessing, worker management, and live hot-reloading.

fenrir run demo_app:app --port 8000 --dev
  • Flags:
    • -H, --host: Host bind address (default: 127.0.0.1).
    • -p, --port: Port number (default: 8000).
    • -w, --workers: Number of concurrent workers (default: 1).
    • -d, --dev / --reload: Active development mode with auto-reload.
    • --disable-dashboard: Disable Asteri built-in dashboard (/asteri-status).

2. fenrir routes

Print a beautiful, colorized structural table of all registered HTTP endpoints, methods, matching handlers, and associated blueprints.

fenrir routes demo_app:app

3. fenrir shell

Instantly spawn an interactive python shell pre-configured with all key framework classes and context loaded (app, request, g, Response, Blueprint, etc.).

fenrir shell demo_app:app

4. fenrir bench

Perform in-memory framework benchmarking directly over ASGI using HTTPX. Eliminates network noise and tests raw pipeline speed under loaded constraints.

fenrir bench demo_app:app -i 1000 -t 5 -p / -m GET

5. fenrir new

Scaffold a complete, cleanly structured new Fenrir project directory in seconds with a premium responsive UI out of the box.

fenrir new my_new_project
cd my_new_project
fenrir run app.py --dev

6. fenrir info

Inspect the environment including Python details, OS details, Pydantic/Asteri versions, active compatibility layers, and route statistics.

fenrir info demo_app:app

7. fenrir monitoring

Manage the built-in monitoring dashboard for health checks and traffic analysis.

fenrir monitoring enable     # Enable monitoring dashboard
fenrir monitoring disable    # Disable monitoring dashboard
fenrir monitoring status     # Show monitoring configuration
fenrir monitoring set-password  # Set new dashboard password

Default credentials:

  • Username: admin
  • Password: changeme

Note: Change the default password in production using fenrir monitoring set-password or set MONITORING_PASSWORD in .env file.


📊 Monitoring API Endpoints

All endpoints require authentication via monitoring token cookie.

GET /monitoring/api/stats

Returns traffic stats, site counts, and uptime start time.

GET /monitoring/api/traffic

Returns today/yesterday traffic comparison.

GET /monitoring/api/alerts?limit=50

Returns recent alerts (limit: 1-500, default 50).

GET /monitoring/api/health

Triggers health check on all monitored sites.

GET /monitoring/api/uptime

Returns uptime percentage for each monitored site.

GET /monitoring/api/response-times?url=...&hours=24

Returns response time history for a specific site (max 168 hours).

GET /monitoring/api/hourly?hours=24

Returns hourly traffic breakdown (max 720 hours).

GET /monitoring/api/summary

Returns comprehensive summary with overview, sites, alerts, and hourly traffic.

POST /monitoring/api/check

Check health of a specific site: {"url": "http://example.com"}


🧪 Comprehensive Test Suite

Fenrir is thoroughly covered by an automated test suite comprising 2,353 tests (plus 6 intentional skips) validating every single component: trie-based routing, streaming body, connection pooling, HTTP/2 push, WebSocket authentication, rate limiting, HTTP digest/OAuth2/OpenID security schemes, PATCH/PUT/DELETE routing, lifespan handling, CSRF auto-token generation, streaming GZip compression, monitoring dashboard, dev mode debug page, ASGI middleware error handling, plugin system, hook system, lightweight ORM, caching system, queue/job system, GraphQL support, gRPC support, performance optimization module, CLI tooling, and the built-in monitoring dashboard.

Coverage: 99% overall (8,103 statements, 2,756 branches) — every fenrir module at 100%. The suite runs automatically via GitHub Actions on every push across Python 3.8 – 3.13, with ruff linting, mypy type-checking, and the coverage report uploaded to Codecov.

Run the test suite locally:

PYTHONPATH=. pytest -q

Output:

=============================== 2353 passed, 6 skipped in 412s ================================

Performance & Benchmarking

  • benchmark.py — in-process comparison of Fenrir vs FastAPI vs Flask vs Falcon vs Sanic (import time, routing, JSON serialization, and ASGI request throughput). Run it in CI via the Benchmark workflow (results posted to the job summary) or locally with python benchmark.py.
  • CodSpeed — micro-benchmarks in tests/benchmarks/ (JSON serialization, router static/parametric/miss matching) track performance regressions on every PR via the CodSpeed workflow. Run them locally with:
    pip install pytest-codspeed
    pytest tests/benchmarks --codspeed
    
  • fenrir bench — quick in-memory benchmark of your own app over ASGI (requires the testing extra):
    fenrir bench demo_app:app -i 1000 -t 5 -p / -m GET
    

CI / CD

Workflow Purpose
test.yml ruff + mypy lint; full suite with coverage on Python 3.8–3.13; Codecov upload
codspeed.yml CodSpeed performance regression tracking
benchmark.yml runs benchmark.py and posts results to the job summary
zizmor.yml security audit of the GitHub Actions workflows themselves
release.yml auto-creates a GitHub Release from the matching CHANGELOG.md section on v* tag push
docker.yml builds and pushes multi-arch (amd64/arm64) images to GHCR
publish.yml publishes wheels/sdist to PyPI on release (trusted publishing)

All workflow actions are pinned to immutable commit SHAs and kept up to date by Dependabot.


🐳 Docker

An official runtime image is published to GitHub Container Registry: ghcr.io/ishikawauta/fenrir.

docker pull ghcr.io/ishikawauta/fenrir

# Run the bundled demo app
docker run -p 8000:8000 ghcr.io/ishikawauta/fenrir

# Run your own app (mount it at /app and point APP_MODULE at it)
docker run -p 8000:8000 \
  -e APP_MODULE=myapp:app \
  -v "$PWD:/app" \
  ghcr.io/ishikawauta/fenrir

Configuration via environment variables: APP_MODULE (default demo_app:app), HOST (default 0.0.0.0), PORT (default 8000), WORKERS (default 1). You can also pass an arbitrary command directly to the container. Build locally with docker build -t fenrir ..


🤝 Contributing

See CONTRIBUTING.md for development setup, test, lint (ruff), type-check (mypy), and coverage instructions.

🔒 Security

Found a vulnerability? Please see SECURITY.md for our disclosure policy and how to report issues.

📜 Code of Conduct

Please note that this project is released with a Contributor Code of Conduct. By participating you agree to abide by its terms.

📜 License

Fenrir is open-sourced software licensed under the MIT License.

Download files

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

Source Distribution

fenrir_framework-4.2.0.tar.gz (1.3 MB view details)

Uploaded Source

Built Distribution

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

fenrir_framework-4.2.0-py3-none-any.whl (1.3 MB view details)

Uploaded Python 3

File details

Details for the file fenrir_framework-4.2.0.tar.gz.

File metadata

  • Download URL: fenrir_framework-4.2.0.tar.gz
  • Upload date:
  • Size: 1.3 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for fenrir_framework-4.2.0.tar.gz
Algorithm Hash digest
SHA256 ad92ae54e25833a84937da56d7352420c1efc9664dd051fa849930bf1cfef8d5
MD5 9cfe490711b3f3338feaa6d9231d8ba5
BLAKE2b-256 5234288f4399caf865fd61f95baf2533f0460da007b99840bab306a14f70bead

See more details on using hashes here.

Provenance

The following attestation bundles were made for fenrir_framework-4.2.0.tar.gz:

Publisher: publish.yml on IshikawaUta/fenrir

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file fenrir_framework-4.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for fenrir_framework-4.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 9f444ddf2a2cee93343391652bcd9a1770ca7214e808a29e31e7ef468e102fe0
MD5 a4ec4a3ba2451078093a339889364c70
BLAKE2b-256 c3fe42dc74bdbe994fffc671df5f224859a24213fe12be910cfce84fc28ff238

See more details on using hashes here.

Provenance

The following attestation bundles were made for fenrir_framework-4.2.0-py3-none-any.whl:

Publisher: publish.yml on IshikawaUta/fenrir

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

4.2.0 This release

2 files

4.1.2

2 files

4.1.1

2 files

4.1.0

2 files

4.0.0

2 files

3.1.3

2 files

3.1.2

2 files

3.1.1

2 files

3.1.0

2 files

3.0.0

2 files

2.3.5

2 files

2.3.4

2 files

2.3.3

2 files

2.3.2

2 files

2.2.2

2 files

1.2.2

2 files

1.2.1

2 files

1.1.1

2 files

1.1.0

2 files

0.1.0

2 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