{"id":50775927,"url":"https://github.com/ishikawauta/fenrir","last_synced_at":"2026-06-12T00:00:21.689Z","repository":{"id":360711307,"uuid":"1251297193","full_name":"IshikawaUta/fenrir","owner":"IshikawaUta","description":"Fenrir is a state-of-the-art, high-performance, hybrid Python web framework built on top of modern ASGI specifications.","archived":false,"fork":false,"pushed_at":"2026-06-11T23:14:55.000Z","size":955,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-11T23:18:10.427Z","etag":null,"topics":["asgi","fenrir-framework","python3","tools","web-framework"],"latest_commit_sha":null,"homepage":"https://fenrir.eksashop.web.id","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/IshikawaUta.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-27T12:46:35.000Z","updated_at":"2026-06-11T23:14:59.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/IshikawaUta/fenrir","commit_stats":null,"previous_names":["ishikawauta/fenrir"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/IshikawaUta/fenrir","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/IshikawaUta%2Ffenrir","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/IshikawaUta%2Ffenrir/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/IshikawaUta%2Ffenrir/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/IshikawaUta%2Ffenrir/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/IshikawaUta","download_url":"https://codeload.github.com/IshikawaUta/fenrir/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/IshikawaUta%2Ffenrir/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34222709,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-11T02:00:06.485Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["asgi","fenrir-framework","python3","tools","web-framework"],"created_at":"2026-06-12T00:00:21.000Z","updated_at":"2026-06-12T00:00:21.666Z","avatar_url":"https://github.com/IshikawaUta.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/IshikawaUta/fenrir/refs/heads/main/logo.jpg\" alt=\"Fenrir Logo\" width=\"500px\"/\u003e\n\u003c/p\u003e\n\n# Fenrir Web Framework\n\n[![PyPI version](https://img.shields.io/pypi/v/fenrir-framework.svg?color=blueviolet)](https://pypi.org/project/fenrir-framework/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)\n[![Python Version](https://img.shields.io/badge/Python-3.8%2B-blue.svg)](https://www.python.org/)\n[![Tests](https://img.shields.io/badge/Tests-568%20Passed-brightgreen.svg)](https://github.com/IshikawaUta/fenrir/actions)\n[![CI](https://github.com/IshikawaUta/fenrir/actions/workflows/test.yml/badge.svg)](https://github.com/IshikawaUta/fenrir/actions/workflows/test.yml)\n[![Performance](https://img.shields.io/badge/Performance-High--Speed%20ASGI-orange.svg)]()\n\n**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.\n\nWhether 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.\n\n---\n\n## 📦 Installation\n\nInstall directly from **PyPI**:\n\n```bash\npip install fenrir-framework\n```\n\nOr install with Redis support for distributed sessions and rate limiting:\n\n```bash\npip install fenrir-framework[redis]\n```\n\nOr install in development mode by cloning the repository:\n\n```bash\ngit clone https://github.com/IshikawaUta/fenrir.git\ncd fenrir\npip install -e .\n```\n\n---\n\n## 🌟 Key Features\n\n*   **⚡ High-Speed ASGI Core**: Extremely low-overhead routing and handler pipeline, achieving massive request throughput.\n*   **🔺 Trie-Based Routing**: O(k) route matching where k = path depth, instead of O(n) linear scan. Handles 1000+ routes efficiently.\n*   **🧩 Framework Hybridization**:\n    *   **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.\n    *   **Flask Paradigm**: Thread/Task-safe context locals (`request`, `g`, `session`), Jinja2 template rendering (`render_template`), and request teardown hooks.\n    *   **Falcon Paradigm**: Class-based resource controllers (`on_get`, `on_post`), before/after hooks, and in-place response mutation.\n    *   **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`).\n    *   **Bottle Paradigm**: Built-in WSGI-to-ASGI wrapper and legacy mount adapter (`app.mount_wsgi()`) to run old WSGI applications at ASGI speeds.\n*   **📖 Auto-Generated OpenAPI Docs**: Interactive **Swagger UI** (`/docs`) and **ReDoc** (`/redoc`) instantly generated from your Pydantic schemas and route metadata.\n*   **🔌 Modern Communications**: Out-of-the-box support for **WebSockets** (with authentication) and **Server-Sent Events (SSE)**.\n*   **🔐 WebSocket Authentication**: `WebSocketTokenAuth` dependency for token-based WebSocket authentication via headers or query parameters.\n*   **🗄️ Connection Pooling**: Built-in generic `ConnectionPool` and `DatabasePool` with health checks, retry logic, and automatic connection recycling.\n*   **🌐 HTTP/2 Push**: `HTTP2Push` utility for server push with Link headers and auto-push decorators.\n*   **⏱️ Advanced Rate Limiting**: Per-IP or per-user rate limiting with optional Redis backend for distributed deployments.\n*   **📦 Streaming Request Body**: `stream_body()` method for memory-efficient processing of large uploads without buffering.\n*   **🗜️ Optimized GZip Compression**: Default compression level 6 (optimal CPU/ratio trade-off) instead of level 9.\n*   **🛠️ Premium CLI Tooling**: Visual route tables, interactive app shell, in-memory benchmarking suite, project scaffolding, and environment system inspection.\n*   **🐍 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.\n\n---\n\n## 🚀 Quick Start (The Hybrid Power)\n\nHere is a simple example (`demo_app.py`) showcasing how Flask, FastAPI, Falcon, and Sanic styles coexist harmoniously in a single application:\n\n```python\nimport logging\nfrom pydantic import BaseModel\nfrom fenrir import (\n    Fenrir, Blueprint, request, g, Depends, Query, Header,\n    render_template, Response, Form, File, UploadFile,\n    WebSocket, WebSocketDisconnect\n)\n\nlogging.basicConfig(level=logging.INFO)\nlogger = logging.getLogger(\"demo\")\n\n# Initialize the Hybrid App\napp = Fenrir(title=\"Fenrir Hybrid Demo\", version=\"2.3.5\")\n\n# --- 1. FastAPI-Style Validation \u0026 DI ---\nclass UserRegister(BaseModel):\n    username: str\n    email: str\n    age: int\n\nasync def verify_api_key(x_api_key: str = Header(default=None)):\n    if x_api_key != \"super-secret-key\":\n        logger.warning(\"Invalid API key attempt\")\n    return x_api_key\n\n# --- 2. Flask-Style Routing \u0026 Context-Locals ---\n@app.get(\"/\")\nasync def home():\n    name = request.args.get(\"name\", \"Fenrir Developer\")\n    return render_template(\"index.html\", name=name)\n\n# --- 3. Falcon-Style Class-Based Resources ---\nclass ItemResource:\n    async def on_get(self, req, resp, item_id: int):\n        resp.status = 200\n        resp.media = {\"item_id\": item_id, \"style\": \"Falcon Resource\"}\n\napp.add_route(\"/items/\u003citem_id:int\u003e\", ItemResource())\n\n# --- 4. Sanic-Style Listeners \u0026 Background Tasks ---\n@app.listener(\"before_server_start\")\nasync def setup_db(app_instance):\n    logger.info(\"Initializing mock database...\")\n\n@app.middleware(\"request\")\nasync def log_request(req):\n    logger.info(f\"Incoming: {req.method} {req.path}\")\n    g.user_role = \"guest\"  # Share state using Flask-style 'g'\n\n# Run with Asteri ASGI server\nif __name__ == \"__main__\":\n    app.run(host=\"127.0.0.1\", port=8000, workers=2)\n```\n\n---\n\n## 🔺 Trie-Based Routing\n\nFenrir v2.3.5 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.\n\n```python\nfrom fenrir import Fenrir\n\napp = Fenrir()\n\n# These routes are indexed in a trie for fast lookup\n@app.get(\"/api/v1/users\")\nasync def list_users(): ...\n\n@app.get(\"/api/v1/users/\u003cint:user_id\u003e\")\nasync def get_user(user_id: int): ...\n\n@app.get(\"/api/v1/posts/\u003cint:post_id\u003e/comments\")\nasync def get_comments(post_id: int): ...\n\n# Route matching is O(k) where k = number of path segments\n# /api/v1/users/42 → checks: api → v1 → users → 42 (parametric)\n```\n\n---\n\n## 🔐 WebSocket Authentication\n\nAuthenticate WebSocket connections using tokens from headers or query parameters:\n\n```python\nfrom fenrir import Fenrir, WebSocket, Depends\nfrom fenrir.security import WebSocketTokenAuth\n\napp = Fenrir()\nauth = WebSocketTokenAuth()\n\n@app.websocket(\"/ws\")\nasync def websocket_handler(websocket: WebSocket, token: str = Depends(auth)):\n    await websocket.accept()\n    await websocket.send_text(f\"Authenticated with token: {token}\")\n    while True:\n        data = await websocket.receive_text()\n        await websocket.send_text(f\"Echo: {data}\")\n```\n\n---\n\n## 🗄️ Connection Pooling\n\nBuilt-in connection pooling for databases and external services:\n\n```python\nfrom fenrir import Fenrir\nfrom fenrir.pool import ConnectionPool\n\napp = Fenrir()\n\n# Create a connection pool\npool = ConnectionPool(\n    create_func=lambda: create_engine(\"sqlite:///db.sqlite3\"),\n    close_func=lambda engine: engine.dispose(),\n    min_size=2,\n    max_size=10,\n)\n\n@app.get(\"/users\")\nasync def list_users():\n    async with pool.acquire() as conn:\n        result = conn.execute(\"SELECT * FROM users\")\n        return {\"users\": [dict(row) for row in result]}\n```\n\n---\n\n## 🌐 HTTP/2 Push\n\nProactively push resources to clients before they request them:\n\n```python\nfrom fenrir import Fenrir\nfrom fenrir.http2 import HTTP2Push\n\napp = Fenrir()\npush = HTTP2Push()\n\n@app.get(\"/\")\nasync def index():\n    return push.push(\n        \"\u003chtml\u003e\u003clink rel='stylesheet' href='/static/style.css'\u003e\u003c/html\u003e\",\n        push_paths=[\"/static/style.css\", \"/static/app.js\"],\n    )\n```\n\n---\n\n## ⏱️ Advanced Rate Limiting\n\nPer-IP or per-user rate limiting with optional Redis backend:\n\n```python\nfrom fenrir import Fenrir\nfrom fenrir.middleware import RateLimitMiddleware\n\napp = Fenrir()\n\n# Per-IP rate limiting\napp.add_middleware(RateLimitMiddleware, max_requests=100, window_seconds=60)\n\n# Per-user rate limiting\ndef user_key(scope):\n    for k, v in scope.get(\"headers\", []):\n        if k == b\"x-user-id\":\n            return v.decode(\"latin-1\")\n    client = scope.get(\"client\")\n    return client[0] if client else \"unknown\"\n\napp.add_middleware(RateLimitMiddleware, key_func=user_key)\n\n# Distributed rate limiting with Redis\nimport redis.asyncio as aioredis\nredis_client = aioredis.Redis()\napp.add_middleware(RateLimitMiddleware, redis_client=redis_client)\n```\n\n---\n\n## 📦 Streaming Request Body\n\nProcess large uploads efficiently without buffering the entire body:\n\n```python\nfrom fenrir import Fenrir, Request\n\napp = Fenrir()\n\n@app.post(\"/upload\")\nasync def upload(request: Request):\n    total_bytes = 0\n    async for chunk in request.stream_body(chunk_size=65536):\n        total_bytes += len(chunk)\n        # Process each chunk without loading entire body into memory\n    return {\"bytes_received\": total_bytes}\n```\n\n---\n\n## 💻 CLI Command Reference\n\nFenrir comes packed with a high-fidelity, visually rich command-line tool. Start the CLI by executing `fenrir` or `python -m fenrir.cli`.\n\n### 1. `fenrir run`\nServe your application locally. Powered by **Asteri**, supporting dynamic multiprocessing, worker management, and live hot-reloading.\n```bash\nfenrir run demo_app:app --port 8000 --dev\n```\n*   **Flags**:\n    *   `-H`, `--host`: Host bind address (default: `127.0.0.1`).\n    *   `-p`, `--port`: Port number (default: `8000`).\n    *   `-w`, `--workers`: Number of concurrent workers (default: `1`).\n    *   `-d`, `--dev` / `--reload`: Active development mode with auto-reload.\n\n### 2. `fenrir routes`\nPrint a beautiful, colorized structural table of all registered HTTP endpoints, methods, matching handlers, and associated blueprints.\n```bash\nfenrir routes demo_app:app\n```\n\n### 3. `fenrir shell`\nInstantly spawn an interactive python shell pre-configured with all key framework classes and context loaded (`app`, `request`, `g`, `Response`, `Blueprint`, etc.).\n```bash\nfenrir shell demo_app:app\n```\n\n### 4. `fenrir bench`\nPerform in-memory framework benchmarking directly over ASGI using `HTTPX`. Eliminates network noise and tests raw pipeline speed under loaded constraints.\n```bash\nfenrir bench demo_app:app -i 1000 -t 5 -p / -m GET\n```\n\n### 5. `fenrir new`\nScaffold a complete, cleanly structured new Fenrir project directory in seconds with a premium responsive UI out of the box.\n```bash\nfenrir new my_new_project\ncd my_new_project\nfenrir run app.py --dev\n```\n\n### 6. `fenrir info`\nInspect the environment including Python details, OS details, Pydantic/Asteri versions, active compatibility layers, and route statistics.\n```bash\nfenrir info demo_app:app\n```\n\n---\n\n## 🧪 Comprehensive Test Suite\n\nFenrir is thoroughly covered by an automated test suite comprising **568 tests** validating every single component, including the new trie-based routing, streaming body, connection pooling, HTTP/2 push, WebSocket authentication, and rate limiting features. The suite runs automatically via **GitHub Actions** on every push across Python **3.8 – 3.13**.\n\nRun the test suite locally:\n\n```bash\nPYTHONPATH=. pytest -v\n```\n\n### Output:\n```text\n=============================== 568 passed, 1 skipped in 6.72s ===============================\n```\n\n---\n\n## 🔄 Changelog\n\n### v2.3.5 — Bug Fix \u0026 Changelog Update\n- Updated changelog to accurately reflect version history\n- All version references synchronized across codebase\n\n### v2.3.4 — Bug Fix Release\n- Fix server crash: `fenrir run` was passing wrong `app_path` (`fenrir.app:_active_app`) to Asteri worker, causing `'NoneType' object is not callable`\n- Fix Python 3.8 support: replaced `asyncio.to_thread` with `fenrir.compat.to_thread` shim\n- Updated all version strings across codebase\n\n### v2.3.3 — 🚫 Retracted\n- Published with incomplete version updates, superseded by v2.3.4\n\n### v2.3.2 — Architecture \u0026 Performance Upgrade\n\nMajor architecture improvements, new features, and performance optimizations:\n\n**Architecture Improvements**\n- **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.\n- **Context Vars Migration**: Removed `sys._fenrir_active_app` hack, replaced with proper `contextvars.ContextVar` for thread/async-task-safe app context.\n\n**New Components**\n- **Connection Pooling (`fenrir.pool`)**: Generic `ConnectionPool` and `DatabasePool` with health checks, retry logic, automatic connection recycling, and configurable pool sizes.\n- **HTTP/2 Push (`fenrir.http2`)**: `HTTP2Push` utility for server push with Link headers, auto-push decorators, and resource type guessing.\n- **WebSocket Authentication (`fenrir.security`)**: `WebSocketTokenAuth` dependency for token-based WebSocket authentication via headers or query parameters.\n\n**New Features**\n- **Streaming Request Body**: `request.stream_body()` method for memory-efficient processing of large uploads without buffering.\n- **Per-User Rate Limiting**: `key_func` parameter in `RateLimitMiddleware` for custom rate limiting keys (user ID, API key, etc.).\n- **Distributed Rate Limiting**: Redis backend support for `RateLimitMiddleware` using sliding window algorithm.\n\n**Performance Optimizations**\n- **GZip Compression Level**: Default `compresslevel` changed from 9 to 6 for optimal CPU/ratio trade-off.\n- **Redis Rate Limiter**: Uses `time.monotonic()` instead of `time.time()` for clock-safe operation, with unique IDs to prevent collisions.\n- **Deprecated API Fix**: Replaced deprecated `asyncio.get_event_loop()` with `asyncio.get_running_loop()` in WSGI adapter.\n\n**Bug Fixes**\n- Fixed missing `import sys` in `app.py` that silently broke root_path detection.\n- Fixed stale `sys._fenrir_active_app` references in `views.py` and `templating.py`.\n- Fixed inconsistent version strings across `pyproject.toml`, `__init__.py`, and `app.py`.\n- Fixed unused `import asyncio` in `falcon.py`.\n- Removed private `Semaphore._value` access from `Pool.stats`.\n\n**New Exports**\n- `RouteTrie`, `WebSocketTokenAuth`, `ConnectionPool`, `DatabasePool`, `HTTP2Push`\n\n### v2.2.2 — Major Feature Update\n\nNew middleware, session backends, pagination, and more:\n\n**New Middleware (`fenrir.middleware`)**\n- **CORSMiddleware**: Full CORS support for HTTP and WebSocket with configurable origins, methods, headers, credentials, and max-age.\n- **GZipMiddleware**: Automatic gzip compression for responses above a configurable size threshold.\n- **RequestIDMiddleware**: Auto-generates unique request IDs or forwards client-provided IDs via configurable header.\n- **RateLimitMiddleware**: Sliding-window rate limiter per client IP with configurable limits and block status code.\n\n**New Session Backends (`fenrir.sessions`)**\n- **InMemorySessionInterface**: In-memory session storage with TTL expiration, suitable for single-process apps and testing.\n- **RedisSessionInterface**: Redis-backed session storage with support for both sync (`fakeredis`) and async (`redis.asyncio`) clients. Install with `pip install fenrir-framework[redis]`.\n\n**New Pagination Utilities (`fenrir.pagination`)**\n- **PaginationParams**: Pydantic model for query parameters (`page`, `page_size`, `sort_by`, `sort_order`).\n- **paginate()**: Utility to paginate SQLAlchemy-style query results with metadata.\n- **paginate_dict()**: Utility to paginate lists of dictionaries.\n\n**New Features**\n- **WebSocket per-route timeout**: `@app.websocket(\"/ws\", timeout=5.0)` raises `WebSocketTimeout` if no message received within the timeout.\n- **Multiple response models per status**: `response_models={200: SuccessModel, 404: ErrorModel}` applies different models based on the actual response status code.\n\n**Improvements**\n- ASGI middleware stack is now built once and cached, with automatic invalidation when new middleware is added.\n- Zero deprecation warnings across the entire test suite (528 tests).\n\n### v1.2.2 — Logo \u0026 Favicon Patch\n\nHigh-quality logo assets and resolved CLI template favicon issues:\n\n- **High-Resolution Logo**: Updated `logo.png` asset to a high-fidelity image for sharper rendering in documentation and templates.\n- **Favicon Resolution**: Ensured favicon is correctly rendered and copied during project scaffolding (`fenrir new`) from the package assets.\n\n### v1.2.1 — Packaging \u0026 Asset Integration Patch\n\nLogo and favicon assets are now properly included in the package distribution:\n\n**Logo Asset Packaging**\n- **Issue**: `fenrir new` command failed to copy logo and favicon files when creating new projects outside the main repository.\n- **Root cause**: Logo files (`logo.png`, `logo.jpg`) were stored in the repository root, not within the `fenrir/` package directory, so they were not included when the package was installed via PyPI.\n- **Fix**: \n  - Moved `logo.png` and `logo.jpg` from repository root to `fenrir/` package directory.\n  - Added `[tool.setuptools.package-data]` configuration in `pyproject.toml` to include image files: `fenrir = [\"logo.png\", \"logo.jpg\"]`.\n  - Updated `fenrir/cli.py` `cmd_new()` function to look for logos in the fenrir package directory first, with fallbacks for development mode.\n- **Result**: All tests pass (528 unit tests). `fenrir new` now works correctly in all environments.\n\n### v1.1.1 — Python 3.8–3.10 Full Compatibility Patch\n\nFive test failures on Python 3.8 CI were identified and patched:\n\n**1. `RuntimeError: Working outside of request context` (session, redirect in sync handlers)**\n- **Root cause**: `loop.run_in_executor()` does **not** propagate `contextvars` by default. Sync route handlers using `session[...]` or `redirect()` lost the request context when moved into the executor thread.\n- **Fix**: `fenrir/compat.py` — polyfill now calls `contextvars.copy_context().run(func)` instead of passing `func` directly to the executor.\n\n**2. `AssertionError: {'user': None} != {'user': 'Alice'}` (Annotated[str, Header()])**\n- **Root cause**: `typing.get_origin(typing_extensions.Annotated[...])` returns `None` on Python 3.8, so `Annotated` parameters were silently ignored during dependency resolution.\n- **Fix**: `fenrir/compat.py` — export `get_origin`/`get_args` from `typing_extensions` (which correctly handles its own `Annotated`). `fenrir/dependencies.py` and `fenrir/openapi.py` now import these from `fenrir.compat`.\n\n**3. `AssertionError: {'content_type': ''} != {'content_type': 'text/plain'}` (file upload)**\n- **Root cause**: `python-multipart \u003c 0.0.21` (installed on Python 3.8–3.10 CI runners) did not pass `content_type` into `File.__init__`, so `file.content_type` did not exist.\n- **Fix**: `fenrir/request.py` — intercepts the parser's `on_header_field`/`on_header_value`/`on_headers_finished` callbacks to capture the `Content-Type` of each multipart part before the `File` object is constructed, and injects it as a fallback.\n\n**4. `AssertionError: 'target' == '/nested/target'` (relative redirect)**\n- Resolved as a side-effect of fix #1 (contextvars propagation restores `request.path` inside the executor thread).\n\n**5. CI timeout on Python 3.9 (gevent build)**\n- The Python 3.9 job was cancelled mid-build because compiling `gevent` took too long. This is an infrastructure concern, not a code issue; no code change required.\n\n### v1.1.0 — CI/CD \u0026 Centering Fix\n- Added **GitHub Actions** workflow for automated testing across Python 3.8–3.13.\n- Fixed centering of `PROJECT CREATED SUCCESSFULLY` badge and logo in scaffolded template.\n- Added **RFC 7231 HEAD** method compliance.\n- Added `itsdangerous` and `python-multipart` as explicit core dependencies.\n\n### v0.1.0 — Initial Release\n- Core ASGI framework with Flask, FastAPI, Sanic, Falcon, and Bottle hybridization.\n- 528 automated unit tests.\n- Premium CLI tooling (`run`, `routes`, `shell`, `bench`, `new`, `info`).\n- Auto-generated OpenAPI/Swagger documentation.\n- WebSocket and Server-Sent Events support.\n\n---\n\n## 📜 License\n\nFenrir is open-sourced software licensed under the [MIT License](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fishikawauta%2Ffenrir","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fishikawauta%2Ffenrir","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fishikawauta%2Ffenrir/lists"}