DEV Community

Cover image for Implementing FastAPI from Scratch Using Only Pure Python
Leapcell
Leapcell

Posted on

Implementing FastAPI from Scratch Using Only Pure Python

Image description

Leapcell: The Best of Serverless Web Hosting

Implementing a FastAPI-like Routing Scheme from WSGI

In the realm of Python web development, FastAPI is widely favored by developers for its efficient and concise routing design, as well as its powerful functionality. FastAPI is built based on the ASGI (Asynchronous Server Gateway Interface) protocol, which is different from the traditional WSGI (Web Server Gateway Interface). This article will explore how to start from WSGI to implement a routing scheme similar to FastAPI, while deeply analyzing key concepts such as WSGI and Uvicorn and their interrelationships.

I. Conceptual Analysis of WSGI, ASGI, and Uvicorn

1.1 WSGI: Web Server Gateway Interface

WSGI is a standard interface in Python web development that defines the communication specification between a web server and a Python web application. Its emergence solves the compatibility problem between different web servers and web frameworks, allowing developers to freely choose web servers and frameworks without worrying about their inability to work together.

WSGI abstracts a web application as a callable object that accepts two parameters: a dictionary containing request information (environ) and a callback function for sending response status codes and header information (start_response). For example, a simple WSGI application can be written as:

def simple_app(environ, start_response):  
    status = '200 OK'  
    response_headers = [('Content-type', 'text/plain')]  
    start_response(status, response_headers)  
    return [b'Hello, World!']  
Enter fullscreen mode Exit fullscreen mode

In this example, environ contains request-related information such as the request method, URL, and HTTP headers; the start_response function is used to set the response status code and header information; and the return value of the function is the response body.

1.2 ASGI: Asynchronous Server Gateway Interface

With the rise of asynchronous programming in Python, especially the widespread use of the asyncio library, the traditional WSGI has been unable to cope with asynchronous I/O operations. ASGI came into being. As an extension of WSGI, it not only supports synchronous applications but also can efficiently handle asynchronous requests, meeting the needs of modern web applications for high performance and high concurrency.

ASGI also defines the communication protocol between the server and the application, but its application callable object supports asynchronous methods. A simple ASGI application example is as follows:

async def simple_asgi_app(scope, receive, send):  
    assert scope['type'] == 'http'  
    await send({  
        'type': 'http.response.start',  
       'status': 200,  
        'headers': [  
            (b'content-type', b'text/plain')  
        ]  
    })  
    await send({  
        'type': 'http.response.body',  
        'body': b'Hello, ASGI!'  
    })  
Enter fullscreen mode Exit fullscreen mode

Here, scope contains information such as the request type (e.g., http); receive is used to receive data sent by the client; and send is used to send response data to the client, with the entire process supporting asynchronous operations.

1.3 Uvicorn: ASGI Server

Uvicorn is a high-performance ASGI server based on Python that can run ASGI applications and is also compatible with WSGI applications. Uvicorn uses libraries such as uvloop and httptools to implement efficient event loops and HTTP parsing, which can fully leverage the asynchronous advantages of ASGI and provide excellent performance.

In actual development, we can use Uvicorn to start ASGI or WSGI applications. For example, to start the above ASGI application:

uvicorn main:app --reload  
Enter fullscreen mode Exit fullscreen mode

Here, main is the name of the Python module containing the application code, app is the ASGI application object defined in the module, and the --reload parameter is used to automatically reload the application when the code changes.

II. Implementing a FastAPI-like Routing Scheme from WSGI

2.1 Basic Principles of the Routing System

FastAPI's routing system binds functions to specific URL paths through decorators. When a request matching the path is received, the corresponding processing function is called and a response is returned. The core idea of implementing a similar routing system in WSGI is to find the corresponding processing function based on the request's URL path and call it to generate a response.

We can define a routing table to store the mapping between URL paths and processing functions. When a request arrives, the matching path is found in the routing table, and then the corresponding processing function is called to generate a response.

2.2 Implementing a Simple WSGI Routing System

Below, we will gradually implement a simple WSGI routing system to simulate FastAPI's routing functions.

First, define an empty routing table:

route_table = {}  
Enter fullscreen mode Exit fullscreen mode

Next, create a decorator to bind functions to URL paths and add them to the routing table:

def route(path):  
    def decorator(func):  
        route_table[path] = func  
        return func  
    return decorator  
Enter fullscreen mode Exit fullscreen mode

Then, implement a WSGI application to find and call the corresponding processing function based on the request path:

def wsgi_app(environ, start_response):  
    path = environ.get('PATH_INFO', '/')  
    if path in route_table:  
        response_body = route_table[path]()  
        status = '200 OK'  
    else:  
        response_body = [b'404 Not Found']  
        status = '404 Not Found'  

    response_headers = [('Content-type', 'text/plain')]  
    start_response(status, response_headers)  
    return response_body  
Enter fullscreen mode Exit fullscreen mode

Now, we can use the defined routing decorator to define processing functions:

@route('/')  
def index():  
    return [b'Welcome to the index page!']  

@route('/about')  
def about():  
    return [b'This is the about page.']  
Enter fullscreen mode Exit fullscreen mode

Finally, use Uvicorn to run this WSGI application:

uvicorn main:wsgi_app --reload  
Enter fullscreen mode Exit fullscreen mode

Through the above steps, we have implemented a simple WSGI routing system that can return corresponding content according to different URL paths, initially simulating FastAPI's routing functions.

2.3 Improving the Routing System

The above routing system is just a basic version and has many shortcomings. For example, it does not support dynamic routing (such as parameterized paths) or request method handling. Below, we will improve it.

Supporting Dynamic Routing

To support dynamic routing, we can use regular expressions to match paths and extract parameters from paths. Import the re module and modify the routing table and routing decorator:

import re  

route_table = {}  

def route(path):  
    def decorator(func):  
        route_table[path] = func  
        return func  
    return decorator  

def dynamic_route(path_pattern):  
    def decorator(func):  
        route_table[path_pattern] = func  
        return func  
    return decorator  
Enter fullscreen mode Exit fullscreen mode

At the same time, modify the WSGI application to handle dynamic routing:

def wsgi_app(environ, start_response):  
    path = environ.get('PATH_INFO', '/')  
    for pattern, handler in route_table.items():  
        if isinstance(pattern, str):  
            if path == pattern:  
                response_body = handler()  
                status = '200 OK'  
                break  
        else:  
            match = re.match(pattern, path)  
            if match:  
                args = match.groups()  
                response_body = handler(*args)  
                status = '200 OK'  
                break  
    else:  
        response_body = [b'404 Not Found']  
        status = '404 Not Found'  

    response_headers = [('Content-type', 'text/plain')]  
    start_response(status, response_headers)  
    return response_body  
Enter fullscreen mode Exit fullscreen mode

Now, we can define dynamic routing processing functions:

@dynamic_route(r'/user/(\d+)')  
def user_detail(user_id):  
    return [f'User {user_id} detail page.'.encode()]  
Enter fullscreen mode Exit fullscreen mode

Supporting Request Methods

To support different request methods (such as GET and POST), we can store the processing functions for different request methods corresponding to each path in the routing table. Modify the routing table and routing decorator as follows:

route_table = {}  

def route(path, methods=['GET']):  
    def decorator(func):  
        if path not in route_table:  
            route_table[path] = {}  
        for method in methods:  
            route_table[path][method] = func  
        return func  
    return decorator  
Enter fullscreen mode Exit fullscreen mode

At the same time, modify the WSGI application to call the corresponding processing function based on the request method:

def wsgi_app(environ, start_response):  
    path = environ.get('PATH_INFO', '/')  
    method = environ.get('REQUEST_METHOD', 'GET')  
    if path in route_table and method in route_table[path]:  
        response_body = route_table[path][method]()  
        status = '200 OK'  
    else:  
        response_body = [b'404 Not Found']  
        status = '404 Not Found'  

    response_headers = [('Content-type', 'text/plain')]  
    start_response(status, response_headers)  
    return response_body  
Enter fullscreen mode Exit fullscreen mode

Now, we can define processing functions that support different request methods:

@route('/login', methods=['GET', 'POST'])  
def login():  
    return [b'Login page.']  
Enter fullscreen mode Exit fullscreen mode

Through the above improvements, our WSGI routing system has become more complete, able to support dynamic routing and different request methods, further approaching FastAPI's routing functions.

III. Summary and Outlook

This article starts from the basic concepts of WSGI, introduces relevant knowledge about ASGI and Uvicorn, and simulates FastAPI's routing functions by gradually implementing a simple WSGI routing system. Through this process, we have gained an in-depth understanding of the communication protocol between web servers and applications, as well as the core principles of routing systems.

Although the WSGI routing system we implemented is still far from FastAPI, it provides a clear idea for us to understand the implementation of routing systems. In the future, we can further improve this routing system, such as adding functions for parsing request bodies and serializing response bodies to make it closer to web frameworks used in actual production environments. At the same time, we can also explore how to extend this routing system to the ASGI environment to fully leverage the advantages of asynchronous programming and improve the performance and concurrency processing capabilities of applications.

The above content details the process of implementing a routing scheme using WSGI. If you think certain parts need to be expanded in more depth or want to add new functions, feel free to let me know.

Leapcell: The Best of Serverless Web Hosting

Recommended as the best platform for deploying Python services: Leapcell

Image description

🚀 Build with Your Favorite Language

Develop effortlessly in JavaScript, Python, Go, or Rust.

🌍 Deploy Unlimited Projects for Free

Only pay for what you use—no requests, no charges.

⚡ Pay-as-You-Go, No Hidden Costs

No idle fees, just seamless scalability.

Image description

📖 Explore Our Documentation

🔹 Follow us on Twitter: @LeapcellHQ

Top comments (0)