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
Installation
pip install robyn
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()
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
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)
Here, we import the static file
object and inside the handler, we pass the path of the HTML file we want to serve.
CRUD API
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)
...
This handler returns the fake_fruit_database
list as a JSON.
def get_item(id: int, db: list):
fruit = {}
for dic in db:
for val in dic.values():
if val == id:
fruit = dic
return fruit
This is just a function to help us look in a list a return the dictionary that has the id
passed in it.
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)
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' : [] }
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.
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"}
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.
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)
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.
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"})
Here, we just remove from the fruit list the dictionary with the id
passed as a parameter.
app.start()
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")
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