DEV Community

Carlos Armando Marcano Vargas
Carlos Armando Marcano Vargas

Posted on • Originally published at carlosmv.hashnode.dev on

Robyn, a web framework for Python, built it on top of Rust.

For this article, we are going to build a server using the Robyn web framework.

It will be a simple server, we are going to learn how to serve an HTML file, and how to perform CRUD operations. We will not be using a database to do it, just a python list as a fake database.

What is Robyn?

"Robyn is a fast, high-performance Python web framework with a Rust runtime. It has been designed to provide near-native Rust throughput while benefiting from the code being written in Python. Robyn is comparable to other products such as Flask, FastAPI, Django, and a web server of choice. One of the key advantages of Robyn is that it does not require an external web server for production, making it more efficient and streamlined".

Sanskar Jethi

More about Robyn, here in Sanskar's blog.

Features

  • Under active development!
  • Written in Rust, btw xD
  • A multithreaded Runtime
  • Extensible
  • A simple API
  • Sync and Async Function Support
  • Dynamic URL Routing
  • Multi Core Scaling
  • WebSockets!
  • Middlewares
  • Hot Reloading
  • Community First and truly FOSS!

Setup

py -m venv venv
cd venv/Scripts
activate

Enter fullscreen mode Exit fullscreen mode

Installation

pip install robyn

Enter fullscreen mode Exit fullscreen mode

Hello World

Let's write a simple endpoint that returns a "Hello World".

We create a file called "app.py".

from robyn import Robyn

app = Robyn( __file__ )

@app.get("/")
async def hello(request):
    return "Hello, world!"

app.start()

Enter fullscreen mode Exit fullscreen mode

We import Robyn and create a Robyn instance, in this case, the app variable.

Then, we define a path operation decorator. When a request goes to the path / using a get operation, the hello function will handle the request and will respond with a "Hello, World!".

To run the server we write in our terminal:

py app.py

Enter fullscreen mode Exit fullscreen mode

Gist

Serving Static Files

index.html

<h1>Hello, World! <h1>


from robyn import Robyn, static_file

app = Robyn( __file__ )

@app.get("/index")
async def get_page(request):
    return static_file("./index.html")

app.start(port=5000)

Enter fullscreen mode Exit fullscreen mode

Here, we import the static file object and inside the handler, we pass the path of the HTML file we want to serve.

Gist

CRUD API

app.py

from robyn import Robyn, jsonify, static_file
from helper import get_item

import json

app = Robyn( __file__ )

fake_fruit_database = [
    {"id":1, "fruit":"Apple"},
    {"id":2, "fruit": "Orange"},
    {"id":3, "fruit": "Pineapple"}
]

...

@app.get("/fruits")
def all_fruits(request):
    return jsonify(fake_fruit_database)

...

Enter fullscreen mode Exit fullscreen mode

This handler returns the fake_fruit_database list as a JSON.

Gist

helper.py

def get_item(id: int, db: list):
    fruit = {}
    for dic in db:
        for val in dic.values():
            if val == id:
                fruit = dic
    return fruit

Enter fullscreen mode Exit fullscreen mode

This is just a function to help us look in a list a return the dictionary that has the id passed in it.

Gist

Get by Id handler

@app.get("/fruit/:id")
def get_fruit(request):
    id = request['params']['id']
    fruit_id = int(id)

    fruit = get_item(fruit_id, fake_fruit_database)

    if fruit == {}:
        return {"status_code":404, "body": "Fruit not Found", "type": "text"}
    else:    
        return jsonify(fruit)

Enter fullscreen mode Exit fullscreen mode

In this handler, we are adding the path parameter id, to retrieve the fruit with the "id" passed.

The request object has this form:

{'params': {'id': ""} , 'headers': {'content-type': 'application/json'}, 'host': 'localhost:5000', 'body' : [] }

Enter fullscreen mode Exit fullscreen mode

To extract the value of id we passed the key params first and then the key id.

If no id is matched, the handler returns the message "Fruit not Found". Otherwise returns a json response.

Gist

Post handler

@app.post("/fruit")
def add_fruit(request):

    body = bytearray(request['body']).decode("utf-8")

    fruit = json.loads(body)

    new_id = fake_fruit_database[-1]['id'] + 1

    fruit_dict = {"id":new_id, "fruit":fruit['fruit']}

    fake_fruit_database.append(fruit_dict)
    return {"status_code":201, "body":jsonify(fruit_dict), "type": "json"}

Enter fullscreen mode Exit fullscreen mode

In this handler, we have to extract the body from the request object and decode it.

Then, we use the json.loads() method, to deserialize and convert the body to a python dictionary.

We generate an id for the fruit we are adding. We create a new dictionary and append it to the fruit list. And return the status code 201 and a JSON as a body response.

Gist

Put handler

@app.put("/fruit/:id")
def update_fruit(request):
    id = request["params"]["id"]
    body = bytearray(request['body']).decode("utf-8")
    fruit = json.loads(body)

    fruit_id = int(id)
    fruit_dict = get_item(fruit_id,fake_fruit_database)

    if fruit_dict == {}:
        return {"status_code":404, "body": "Fruit not Found", "type": "text"}
    else:    
        fruit_dict['fruit'] = fruit['fruit']
        return jsonify(fruit)

Enter fullscreen mode Exit fullscreen mode

We need to extract the id parameter and the body for this handler.

Then, we search for the fruit with the id passed and replace the fruit value with the one in the request body.

Gist

Delete handler

@app.delete("/fruit/:id")
def delete_fruit(request):
    id = request["params"]["id"]

    fruit_id = int(id)
    fruit_dict = get_item(fruit_id,fake_fruit_database)

    if fruit_dict == {}:
        return {"status_code":404, "body": "Fruit not Found", "type": "text"}
    else:    
        fake_fruit_database.remove(fruit_dict)
        return jsonify({"Message":"Fruit was deleted"})

Enter fullscreen mode Exit fullscreen mode

Here, we just remove from the fruit list the dictionary with the id passed as a parameter.

Gist

app.start()

Enter fullscreen mode Exit fullscreen mode

The default PORT and url are 5000 and127.0.0.1 respectively.

If we want to start the server on a different port and URL, we have to pass the values to app.start().

app.start(port=8000, url="0.0.0.0")

Enter fullscreen mode Exit fullscreen mode

Conclusion

I have been learning Rust for a few months and I thought the approach of using Rust to build a Python web framework and leverage the power of Rust is interesting. I was really enthusiastic to use Robyn and write about it. And I am excited to follow its development.

Thank you for taking the time to read this article.

If you have any recommendations about other packages, architectures, how to improve my code, my English, or anything; please leave a comment or contact me through Twitter, or LinkedIn.

The source code is here

Reference

Top comments (0)