A hybrid Python web framework combining Flask, FastAPI, Bottle, Falcon, and Sanic
Project description
Fenrir Web Framework
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
Or install with Redis support for distributed sessions and rate limiting:
pip install fenrir-framework[redis]
Or install in development mode by cloning the repository:
git clone https://github.com/IshikawaUta/fenrir.git
cd fenrir
pip install -e .
๐ 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,
Annotatedtype decorators, automated parameter resolution (Query,Path,Header,Cookie,Body), dynamic dependency injection (Depends), and automatedresponse_modelserialization. - 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.modulespatching (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.
- FastAPI Paradigm: Native Pydantic v2 data validation,
- ๐ 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:
WebSocketTokenAuthdependency for token-based WebSocket authentication via headers or query parameters. - ๐๏ธ Connection Pooling: Built-in generic
ConnectionPoolandDatabasePoolwith health checks, retry logic, and automatic connection recycling. - ๐ HTTP/2 Push:
HTTP2Pushutility 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:
BodyLimitMiddlewareto reject oversized request bodies and prevent DoS attacks. - ๐ CSRF Protection:
CSRFMiddlewarefor 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:
GZipMiddlewarecompresses each chunk on-the-fly forStreamingResponse, 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.
- ๐ Python 3.8โ3.13 Compatible: Full backward compatibility ensured via
typing_extensionspolyfills forAnnotated,get_origin,get_args; and acontextvars-awareasyncio.to_threadshim.
๐ 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="3.1.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)):
if x_api_key != "super-secret-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 v3.1.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}
๐ป 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-passwordor setMONITORING_PASSWORDin.envfile.
๐ 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 669 tests validating every single component, including the new 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 (including uptime, response time history, hourly traffic, and summary endpoints), and all v3.1.0 improvements. The suite runs automatically via GitHub Actions on every push across Python 3.8 โ 3.13.
Run the test suite locally:
PYTHONPATH=. pytest -v
Output:
=============================== 669 passed, 1 skipped in 36.36s ===============================
๐ Changelog
v3.1.0 โ Monitoring Features
Added built-in monitoring dashboard:
New Features:
- Monitoring dashboard with health checks, traffic analysis, and alerts
- CLI commands for enable/disable monitoring
- bcrypt password hashing for secure authentication
- Async health checks using thread pool
- Uptime statistics endpoint (
/monitoring/api/uptime) - Response time history endpoint (
/monitoring/api/response-times) - Hourly traffic breakdown endpoint (
/monitoring/api/hourly) - Comprehensive summary endpoint (
/monitoring/api/summary) - Enhanced dashboard with uptime percentage display
--disable-dashboardflag for Asteri built-in dashboard
Bug Fixes:
- Fixed unused import in monitoring routes
- Fixed invalid integer parsing in alerts API
- Fixed blocking call in health check (now async)
- Fixed Response constructor parameter mismatch
v3.0.0 โ Major Bug Fix & Architecture Release
Fixed 21 bugs, added 46 new tests, and introduced architecture improvements:
HIGH SEVERITY Fixes (7)
- Fixed
routing.py: Converter keyword as param name producingparam_name="". - Fixed
routing.py:<path>converter now recurses into child nodes at all possible depths, enabling routes like/api/<path:resource>/detailsto match correctly. - Fixed
app.py: Global teardown functions no longer run twice โ deduplication viaseenset. - Fixed
app.py: WebSocket handlers now properly set_app_ctx_varsocurrent_appworks inside websocket handlers. - Fixed
dependencies.py: Plain default params (e.g.page: int = 1) now return the actual default value instead ofNone. - Fixed
dependencies.py:Annotated[T, Query()]with function default now preserves the default value. - Fixed
compat.py: WSGI response body iteration now runs in a thread executor viarun_in_executor, preventing event loop blocking.
MEDIUM SEVERITY Fixes (8)
- Fixed
app.py:_coerce_responseno longer infinitely recurses on 4+ element tuples โ serializes as JSON array. - Fixed
app.py: Streaming error now always sendsmore_body=Falseframe, ensuring complete ASGI responses. - Fixed
response.py:textproperty returns""instead ofNonefor empty bodies. - Fixed
security.py:HTTPDigestnow returns a parsed dict instead of raw header string. - Fixed
openapi.py: Path parameter detection now checks bothparam_nameandalias. - Fixed
pagination.py: URL building now deduplicates query params usingurllib.parseinstead of blind appending. - Fixed
helpers.py:Content-Dispositionfilename is now properly quoted. - Fixed
signals.py: Async signal results are now collected as task objects.
LOW SEVERITY Fixes (6)
- Removed dead code
_WsgiMountexception and_wsgi_handlerfromapp.py. - Fixed unused
respvariable in websocket path โ now passed toresolve_parameters. - Removed dead code
regex_segmentsfromrouting.pyRouteTrie.insert(). - Fixed
templating.py: Removed destructiveos.makedirsside effect fromJinja2Renderer.__init__. - Fixed
context.py: Addedhasattrguard fordo_teardown_appcontextinAppContext.__exit__. - Fixed
views.py:req.methodcan no longer causeAttributeErrorโ defaults to"GET"whenNone.
Architecture Improvements
- Added
BodyLimitMiddleware: Rejects requests exceeding configurable max body size (default 10 MB), now enforces actual body size via chunk monitoring (not just Content-Length header). - Added
CSRFMiddleware: CSRF token validation for state-changing HTTP methods, with automatic token generation and cookie injection (auto_generate=Trueby default). - Fixed
GZipMiddleware: Streaming compression forStreamingResponse(on-the-fly chunk compression); fixed_is_compressible()to only compress explicit text-based types (was incorrectly compressing allapplication/*andimage/*). - Added
inspect.signature()caching via_get_cached_signature()independencies.py, now used byresolve_parameters()for optimal parameter resolution. - Added OpenAPI schema caching in
app.openapi()โ cached after first call, invalidated on route changes. - Fixed
CORSMiddleware: Wildcard origin with credentials now echoes the specific origin per CORS spec. - Fixed
RateLimitMiddleware: Redis backend now checks limit before adding request (matching in-memory behavior). - Fixed
app.py: Lifespan handler now returns after startup failure instead of looping forever.
New Tests (46 tests)
- PATCH/PUT/DELETE method routing + 405 on wrong method (7 tests)
- HTTPDigest auth parsing: success, missing header, wrong scheme, auto_error=False, field parsing (5 tests)
- OAuth2AuthorizationCodeBearer: success, missing, auto_error=False (3 tests)
- OpenIDConnect: success, missing, wrong scheme, auto_error=False, model (5 tests)
- Rate limiting via Redis backend: under limit, over limit, different keys (3 tests)
- GZip + streaming response (2 tests)
- 4+ element tuple response coercion (4 tests)
- Malformed JSON body + wrong content-type with strict mode (3 tests)
- Lifespan scope handling: startup/shutdown, startup failure (2 tests)
- CORS wildcard + credentials edge case (2 tests)
- Signature caching verification (3 tests)
- OpenAPI schema caching (2 tests)
- CSRF middleware auto-token generation: GET sets cookie, POST without token rejected, POST with valid token accepted, auto_generate=False (4 tests)
- GZip streaming compression: chunks compressed on-the-fly (1 test)
v2.3.5 โ Bug Fix & Changelog Update
- Updated changelog to accurately reflect version history
- All version references synchronized across codebase
v2.3.4 โ Bug Fix Release
- Fix server crash:
fenrir runwas passing wrongapp_path(fenrir.app:_active_app) to Asteri worker, causing'NoneType' object is not callable - Fix Python 3.8 support: replaced
asyncio.to_threadwithfenrir.compat.to_threadshim - Updated all version strings across codebase
v2.3.3 โ ๐ซ Retracted
- Published with incomplete version updates, superseded by v2.3.4
v2.3.2 โ Architecture & Performance Upgrade
Major architecture improvements, new features, and performance optimizations:
Architecture Improvements
- Trie-Based Routing: Replaced O(n) linear route matching with O(k) trie-based routing. Route lookup now scales with path depth, not total route count.
- Context Vars Migration: Removed
sys._fenrir_active_apphack, replaced with propercontextvars.ContextVarfor thread/async-task-safe app context.
New Components
- Connection Pooling (
fenrir.pool): GenericConnectionPoolandDatabasePoolwith health checks, retry logic, automatic connection recycling, and configurable pool sizes. - HTTP/2 Push (
fenrir.http2):HTTP2Pushutility for server push with Link headers, auto-push decorators, and resource type guessing. - WebSocket Authentication (
fenrir.security):WebSocketTokenAuthdependency for token-based WebSocket authentication via headers or query parameters.
New Features
- Streaming Request Body:
request.stream_body()method for memory-efficient processing of large uploads without buffering. - Per-User Rate Limiting:
key_funcparameter inRateLimitMiddlewarefor custom rate limiting keys (user ID, API key, etc.). - Distributed Rate Limiting: Redis backend support for
RateLimitMiddlewareusing sliding window algorithm.
Performance Optimizations
- GZip Compression Level: Default
compresslevelchanged from 9 to 6 for optimal CPU/ratio trade-off. - Redis Rate Limiter: Uses
time.monotonic()instead oftime.time()for clock-safe operation, with unique IDs to prevent collisions. - Deprecated API Fix: Replaced deprecated
asyncio.get_event_loop()withasyncio.get_running_loop()in WSGI adapter.
Bug Fixes
- Fixed missing
import sysinapp.pythat silently broke root_path detection. - Fixed stale
sys._fenrir_active_appreferences inviews.pyandtemplating.py. - Fixed inconsistent version strings across
pyproject.toml,__init__.py, andapp.py. - Fixed unused
import asyncioinfalcon.py. - Removed private
Semaphore._valueaccess fromPool.stats.
New Exports
RouteTrie,WebSocketTokenAuth,ConnectionPool,DatabasePool,HTTP2Push
v2.2.2 โ Major Feature Update
New middleware, session backends, pagination, and more:
New Middleware (fenrir.middleware)
- CORSMiddleware: Full CORS support for HTTP and WebSocket with configurable origins, methods, headers, credentials, and max-age.
- GZipMiddleware: Automatic gzip compression for responses above a configurable size threshold.
- RequestIDMiddleware: Auto-generates unique request IDs or forwards client-provided IDs via configurable header.
- RateLimitMiddleware: Sliding-window rate limiter per client IP with configurable limits and block status code.
New Session Backends (fenrir.sessions)
- InMemorySessionInterface: In-memory session storage with TTL expiration, suitable for single-process apps and testing.
- RedisSessionInterface: Redis-backed session storage with support for both sync (
fakeredis) and async (redis.asyncio) clients. Install withpip install fenrir-framework[redis].
New Pagination Utilities (fenrir.pagination)
- PaginationParams: Pydantic model for query parameters (
page,page_size,sort_by,sort_order). - paginate(): Utility to paginate SQLAlchemy-style query results with metadata.
- paginate_dict(): Utility to paginate lists of dictionaries.
New Features
- WebSocket per-route timeout:
@app.websocket("/ws", timeout=5.0)raisesWebSocketTimeoutif no message received within the timeout. - Multiple response models per status:
response_models={200: SuccessModel, 404: ErrorModel}applies different models based on the actual response status code.
Improvements
- ASGI middleware stack is now built once and cached, with automatic invalidation when new middleware is added.
- Zero deprecation warnings across the entire test suite (528 tests).
v1.2.2 โ Logo & Favicon Patch
High-quality logo assets and resolved CLI template favicon issues:
- High-Resolution Logo: Updated
logo.pngasset to a high-fidelity image for sharper rendering in documentation and templates. - Favicon Resolution: Ensured favicon is correctly rendered and copied during project scaffolding (
fenrir new) from the package assets.
v1.2.1 โ Packaging & Asset Integration Patch
Logo and favicon assets are now properly included in the package distribution:
Logo Asset Packaging
- Issue:
fenrir newcommand failed to copy logo and favicon files when creating new projects outside the main repository. - Root cause: Logo files (
logo.png,logo.jpg) were stored in the repository root, not within thefenrir/package directory, so they were not included when the package was installed via PyPI. - Fix:
- Moved
logo.pngandlogo.jpgfrom repository root tofenrir/package directory. - Added
[tool.setuptools.package-data]configuration inpyproject.tomlto include image files:fenrir = ["logo.png", "logo.jpg"]. - Updated
fenrir/cli.pycmd_new()function to look for logos in the fenrir package directory first, with fallbacks for development mode.
- Moved
- Result: All tests pass (528 unit tests).
fenrir newnow works correctly in all environments.
v1.1.1 โ Python 3.8โ3.10 Full Compatibility Patch
Five test failures on Python 3.8 CI were identified and patched:
1. RuntimeError: Working outside of request context (session, redirect in sync handlers)
- Root cause:
loop.run_in_executor()does not propagatecontextvarsby default. Sync route handlers usingsession[...]orredirect()lost the request context when moved into the executor thread. - Fix:
fenrir/compat.pyโ polyfill now callscontextvars.copy_context().run(func)instead of passingfuncdirectly to the executor.
2. AssertionError: {'user': None} != {'user': 'Alice'} (Annotated[str, Header()])
- Root cause:
typing.get_origin(typing_extensions.Annotated[...])returnsNoneon Python 3.8, soAnnotatedparameters were silently ignored during dependency resolution. - Fix:
fenrir/compat.pyโ exportget_origin/get_argsfromtyping_extensions(which correctly handles its ownAnnotated).fenrir/dependencies.pyandfenrir/openapi.pynow import these fromfenrir.compat.
3. AssertionError: {'content_type': ''} != {'content_type': 'text/plain'} (file upload)
- Root cause:
python-multipart < 0.0.21(installed on Python 3.8โ3.10 CI runners) did not passcontent_typeintoFile.__init__, sofile.content_typedid not exist. - Fix:
fenrir/request.pyโ intercepts the parser'son_header_field/on_header_value/on_headers_finishedcallbacks to capture theContent-Typeof each multipart part before theFileobject is constructed, and injects it as a fallback.
4. AssertionError: 'target' == '/nested/target' (relative redirect)
- Resolved as a side-effect of fix #1 (contextvars propagation restores
request.pathinside the executor thread).
5. CI timeout on Python 3.9 (gevent build)
- The Python 3.9 job was cancelled mid-build because compiling
geventtook too long. This is an infrastructure concern, not a code issue; no code change required.
v1.1.0 โ CI/CD & Centering Fix
- Added GitHub Actions workflow for automated testing across Python 3.8โ3.13.
- Fixed centering of
PROJECT CREATED SUCCESSFULLYbadge and logo in scaffolded template. - Added RFC 7231 HEAD method compliance.
- Added
itsdangerousandpython-multipartas explicit core dependencies.
v0.1.0 โ Initial Release
- Core ASGI framework with Flask, FastAPI, Sanic, Falcon, and Bottle hybridization.
- 528 automated unit tests.
- Premium CLI tooling (
run,routes,shell,bench,new,info). - Auto-generated OpenAPI/Swagger documentation.
- WebSocket and Server-Sent Events support.
๐ License
Fenrir is open-sourced software licensed under the MIT 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 Distribution
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 fenrir_framework-3.1.0.tar.gz.
File metadata
- Download URL: fenrir_framework-3.1.0.tar.gz
- Upload date:
- Size: 1.2 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
183796b9ad92b6d88a3f7deaa5be130f8c1a429029c4546b9fb473576a6c00f8
|
|
| MD5 |
de9d770fd029bab4335f215b2733d030
|
|
| BLAKE2b-256 |
20661c22d6566aaa6218a260a7eeb816e53d9bcc116f9b51a1f22926b5fffc88
|
Provenance
The following attestation bundles were made for fenrir_framework-3.1.0.tar.gz:
Publisher:
publish.yml on IshikawaUta/fenrir
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
fenrir_framework-3.1.0.tar.gz -
Subject digest:
183796b9ad92b6d88a3f7deaa5be130f8c1a429029c4546b9fb473576a6c00f8 - Sigstore transparency entry: 1812461273
- Sigstore integration time:
-
Permalink:
IshikawaUta/fenrir@955ca3b67115f82b09d462fef58dae2327fb87b4 -
Branch / Tag:
refs/tags/v3.1.0 - Owner: https://github.com/IshikawaUta
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@955ca3b67115f82b09d462fef58dae2327fb87b4 -
Trigger Event:
release
-
Statement type:
File details
Details for the file fenrir_framework-3.1.0-py3-none-any.whl.
File metadata
- Download URL: fenrir_framework-3.1.0-py3-none-any.whl
- Upload date:
- Size: 1.2 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
169c92fe254a9d71e9db8a4f94839347736a4c50cfde5971105e4d59b0d38600
|
|
| MD5 |
2f2cda0c8ce36276d83d9af20d7f7a0d
|
|
| BLAKE2b-256 |
4eb62ea9bedb6bd5b125538af4f4f9d9a8956e247944c17108f4c947e977c8cd
|
Provenance
The following attestation bundles were made for fenrir_framework-3.1.0-py3-none-any.whl:
Publisher:
publish.yml on IshikawaUta/fenrir
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
fenrir_framework-3.1.0-py3-none-any.whl -
Subject digest:
169c92fe254a9d71e9db8a4f94839347736a4c50cfde5971105e4d59b0d38600 - Sigstore transparency entry: 1812461674
- Sigstore integration time:
-
Permalink:
IshikawaUta/fenrir@955ca3b67115f82b09d462fef58dae2327fb87b4 -
Branch / Tag:
refs/tags/v3.1.0 - Owner: https://github.com/IshikawaUta
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@955ca3b67115f82b09d462fef58dae2327fb87b4 -
Trigger Event:
release
-
Statement type: