Skip to main content

RahiFrame — A Python web framework built from scratch.

Project description

RahiFrame

Python License Status

A Python web framework engineered from scratch — built to understand exactly how modern web frameworks work under the hood.

Most developers use Flask or Django without knowing what happens beneath @app.route. This project tears that abstraction apart and rebuilds it piece by piece.


Project Structure

python-framework-from-scratch/
│
├── rahiframe/
│   ├── __init__.py
│   ├── api.py           # Core: routing, views, middleware, templates
│   ├── middleware.py    # Middleware base class and pipeline
│   └── response.py     # Response class with automatic content-type handling
│
├── static/
│   └── main.css         # Styles served via WhiteNoise
├── templates/
│   └── index.html       # Jinja2 template
├── app.py               # Example application demonstrating all features
├── conftest.py          # Pytest fixtures
└── test_rahiframe.py    # Full test suite

What This Is

RahiFrame is a fully functional WSGI-based Python web framework built without relying on any existing framework. It implements the core primitives every web framework needs:

  • Routing engine — URL pattern matching with dynamic parameters
  • Function-based & class-based views — both handler styles supported
  • Middleware pipeline — composable layers that wrap every request
  • Request/Response cycle — automatic content-type handling for JSON, HTML, and plain text
  • Template rendering — Jinja2-powered HTML templating
  • Static file serving — WhiteNoise integration for CSS and assets
  • HTTP method control — restrict routes to specific methods
  • Exception handling — custom error handlers
  • Test client — built-in session for integration tests without a running server

Setup

Install dependencies:

pip install gunicorn webob parse requests requests-wsgi-adapter jinja2 whitenoise

Run the example app:

python -m gunicorn app:app

Run tests:

python -m pytest test_rahiframe.py -v

Features & Usage

Function-Based Routes

from rahiframe.api import API

app = API()

@app.route("/home")
def home(request, response):
    response.text = "Hello from the HOME page"

Dynamic URL Parameters

@app.route("/hello/{name}")
def greeting(request, response, name):
    response.text = f"Hello, {name}!"

Visit /hello/RahiHello, Rahi!

Class-Based Views

Group related HTTP methods under one class:

@app.route("/books")
class BooksResource:
    def get(self, req, resp):
        resp.json = {"books": ["Book 1", "Book 2", "Book 3"]}

    def post(self, req, resp):
        resp.json = {"message": "Book created successfully"}

With typed URL parameters:

@app.route("/users/{id:d}")
class UserResource:
    def get(self, req, resp, id):
        resp.text = f"Get user {id}"

    def put(self, req, resp, id):
        resp.text = f"Update user {id}"

    def delete(self, req, resp, id):
        resp.text = f"Delete user {id}"

Django-Style Route Registration

Register routes without decorators:

def sample_handler(req, resp):
    resp.text = "Django-style route registration"

app.add_route("/sample", sample_handler)

Response Types

RahiFrame automatically sets the correct Content-Type header based on which property you set:

# Plain text → Content-Type: text/plain
@app.route("/text")
def text_handler(req, resp):
    resp.text = "This is plain text"

# JSON → Content-Type: application/json
@app.route("/json")
def json_handler(req, resp):
    resp.json = {"name": "data", "type": "JSON"}

# HTML → Content-Type: text/html
@app.route("/template")
def template_handler(req, resp):
    resp.html = app.template("index.html", context={"title": "RahiFrame", "name": "RahiFrame"})

Template Rendering

Templates use Jinja2 and live in the templates/ folder.

templates/index.html:

<html>
  <header>
    <title>{{ title }}</title>
    <link rel="stylesheet" type="text/css" href="static/main.css">
  </header>
  <body>
    <h1>The name of the framework is {{ name }}</h1>
  </body>
</html>

Render it from a handler:

app = API(templates_dir="templates")

@app.route("/template")
def template_handler(req, resp):
    resp.html = app.template(
        "index.html",
        context={"title": "RahiFrame", "name": "RahiFrame"}
    )

Static Files

Static files live in static/ and are served automatically via WhiteNoise:

app = API(static_dir="static")

static/main.css:

body {
    background-color: teal;
    font-family: Arial, sans-serif;
    margin: 20px;
}

h1 {
    color: white;
    text-align: center;
}

Access at: http://localhost:8000/static/main.css

Referenced directly from templates:

<link rel="stylesheet" type="text/css" href="static/main.css">

HTTP Method Control

@app.route("/api/products", allowed_methods=["GET", "POST"])
def products_api(request, response):
    if request.method == "GET":
        response.text = "List products"
    elif request.method == "POST":
        response.text = "Create product"

# Also works with add_route
app.add_route("/api/admin", admin_handler, allowed_methods=["PATCH"])

Middleware

Subclass Middleware and implement process_request and/or process_response:

from rahiframe.middleware import Middleware

class SimpleCustomMiddleware(Middleware):
    def process_request(self, req):
        print("Processing request", req.url)

    def process_response(self, req, resp):
        print("Processing response", req.url)

app.add_middleware(SimpleCustomMiddleware)

Stack multiple middleware layers — they execute in order:

import time

class RequestTimingMiddleware(Middleware):
    def process_request(self, req):
        req.start_time = time.time()

    def process_response(self, req, resp):
        if hasattr(req, "start_time"):
            duration = time.time() - req.start_time
            resp.headers["X-Response-Time"] = f"{duration:.4f}s"

app.add_middleware(RequestTimingMiddleware)

Exception Handling

def custom_exception_handler(request, response, exception_cls):
    response.text = f"Error occurred: {str(exception_cls)}"

app.add_exception_handler(custom_exception_handler)

Testing

RahiFrame ships with a built-in test client — no running server needed.

import pytest
from rahiframe.api import API

@pytest.fixture
def app():
    return API()

@pytest.fixture
def client(app):
    return app.test_session()

def test_homepage(app, client):
    @app.route("/home")
    def home(request, response):
        response.text = "Hello!"

    res = client.get("http://testserver/home")
    assert res.status_code == 200
    assert res.text == "Hello!"

Static file tests use temporary directories via tmpdir_factory — your real static/ folder is never touched during testing. Tests are fully isolated:

def test_assets_are_served(tmpdir_factory):
    static_dir = tmpdir_factory.mktemp("static")  # temporary, deleted after test
    ...

Run the full suite:

python -m pytest test_rahiframe.py -v

How It Works

WSGI (Web Server Gateway Interface) is the contract between a Python web app and a server like Gunicorn. At its core, every WSGI app is just a callable:

def app(environ, start_response):
    start_response("200 OK", [("Content-Type", "text/plain")])
    return [b"Hello World"]

RahiFrame wraps this contract into a clean, usable API. Here's the full request lifecycle:

HTTP Request
     │
     ▼
Gunicorn (WSGI Server)
     │  calls app(environ, start_response)
     ▼
Middleware Stack
     │  process_request() on each layer (outermost first)
     ▼
API.__call__()
     │  parses environ → WebOb Request object
     ▼
Router
     │  matches URL pattern → resolves handler
     ▼
Handler (function-based or class-based view)
     │  populates Response object
     ▼
Middleware Stack
     │  process_response() on each layer (innermost first)
     ▼
Response.__call__()
     │  sets Content-Type, status code, body
     ▼
HTTP Response

Why Build a Framework?

Using Flask or Django without understanding their internals is like driving a car without knowing how an engine works — fine until something breaks. Building RahiFrame taught me:

  • How WSGI works and why it exists
  • How URL routing is implemented with pattern matching
  • How middleware pipelines compose behavior without touching core logic
  • How HTTP responses are constructed and content-typed automatically
  • How Jinja2 templating integrates into a request/response cycle
  • How static files are served via WhiteNoise
  • How test clients simulate HTTP without a network

This isn't meant to replace Flask. It's meant to make you understand why Flask exists.


Author

Mottakin Kamal Rahi
📧 mottakin.chy@gmail.com
🐙 github.com/Mottakinrahi


License

MIT — free to use, learn from, and build on.

Project details


Download files

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

Source Distribution

rahiframe-0.0.1.tar.gz (8.8 kB view details)

Uploaded Source

Built Distribution

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

rahiframe-0.0.1-py3-none-any.whl (7.2 kB view details)

Uploaded Python 3

File details

Details for the file rahiframe-0.0.1.tar.gz.

File metadata

  • Download URL: rahiframe-0.0.1.tar.gz
  • Upload date:
  • Size: 8.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.5

File hashes

Hashes for rahiframe-0.0.1.tar.gz
Algorithm Hash digest
SHA256 7a32ebfbc8d0c9a2fcd95bd4e8d93bf9b00a9ec02882fea8eb310b4d340aaa0b
MD5 bf82efc2c415573a14438ad7124d30a1
BLAKE2b-256 56867ac7edce657785590a2ba32c9588b276f6ff38fd96396d023b0abdadeb0b

See more details on using hashes here.

File details

Details for the file rahiframe-0.0.1-py3-none-any.whl.

File metadata

  • Download URL: rahiframe-0.0.1-py3-none-any.whl
  • Upload date:
  • Size: 7.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.5

File hashes

Hashes for rahiframe-0.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 44fb1ed4a955c8a71041ff61f8e6ad89d81e7a4f803dd97cc19aeb16d8c61801
MD5 f6c84a79bf9d606b64c5c2274910d325
BLAKE2b-256 78985d385e27c607abe757f917ca1afb76f9a4ccaa8b12ef510125070ce035b1

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page