Is Sanic python web framework the new Flask?

What the hell is Sanic web framework anyway?

Intro

I recently came across Sanic and I was surprised why I never heard about it before. Sanic is a micro-framework which is quite close to Flask. It is quite extensible with a lot of extensions and middleware being created for it. In this article we will discuss the pros and cons of Sanic and we will see in which scenarios Sanic might replace Flask.

The Facts

At the time this article was written, Sanic on github has around 13.5k stars, 226 contributors and is used by 2.7k repos, which for a web framework that is fairly new (First long term release was in December 2018) it has definitely gained a lot of attention.

The Pros

Perfromance comparision between Flask and Sanic
  1. Fast learning curve since Sanic looks a lot like Flask, if you come from Flask background, it should be quite easy to work with Sanic, it even uses the same naming convention as Flask (e.g Blueprints)
  2. Sanic can handle requests Asynchronously. Flask Can’t yet.
  3. Although Sanic is fairly new, there is a lot of packages (middleware or extenstions) created to support Sanic.
  4. This is a little tip from me, for every language/framework there is a github repo as a collection of all of the awesome packages related to that language/framework. Awesome-python, awesome-django, awesome-flask .. etc. Luckily there is Awesome-Sanic and it contains all of the great extensions/middleware for sanic.
  5. Extenstions include (ORM/SQLAlchemy, Auth, Caching-Redis, Queues .. etc)
  6. It is quite easy to create a middlewares/extension for Sanic

The Cons

  1. I am not a big fan of the readthedocs type of documentation, it is 2020 already!, take a look at Django-documentation or FastAPI-documentation
  2. Like any new framework, I dont know how does it perform in production, Flask is battle tested.

Lets create a simple app using Sanic

I use pipenv to manage packages and virtualenv, but you can use any package manager you like

Installing sanic

pipenv install sanic

main.py

from sanic import Sanic
from sanic.response import json

app = Sanic(__name__)
@app.route("/")
async def test(request):
return json({"message": "Hello_world"})

if __name__ == "__main__":
app.run(host="0.0.0.0", port=8000)

Run it

pipenv run python main.py

check your browser at http://localhost:8000

Now lets add more endpoints


from sanic import Sanic
from sanic import response
from sanic.response import json

app = Sanic(__name__)


@app.route("/", methods=["GET"])
async def index(request):
return json({"message": "Hello_world"})


@app.route("/post", methods=["POST"])
async def post(request):
return json({"message": request.json})


@app.route("/empty", methods=["PUT"])
async def empty_response(request):
return response.empty()


if __name__ == "__main__":
app.run(host="0.0.0.0", port=8000)

Lets see how easy to create a middleware

we will create a middleware to change the camelCase from snake_case to camelCase for JSON responses/response.

so if a request is coming with a camelCase JSON it will be converted to snake_case, and viseversa for response

lets create a new file calledsanic_camelcase_middleware.py

and add the following code

from json import loads, dumps
from humps import camelize, decamelize


class Camelize:
def __init__(self, app):
@app.middleware("request")
async def request_to_snake_case(request):

if request.body:
request.body = bytes(dumps(decamelize(loads(request.body))), "utf-8")

@app.middleware("response")
async def response_to_camel(request, response):

if response.body:
response.body = bytes(dumps(camelize(loads(response.body))), "utf-8")

we are using pyhumps to handle the camilization/decamelization of the body.

so we must install that

pipenv install pyhumps

now lets import this code in our main.py

# -*- coding: utf-8 -*-
"""Sample main.py used for running tests for middleware

"""
from sanic import Sanic
from sanic import response
from sanic.response import json
from sanic_camelcase_middleware import Camelize

app = Sanic(__name__)

Camelize(app)


@app.route("/", methods=["GET"])
async def index(request):
return json({"is_camelcase": True, "message": "Hello_world"})


@app.route("/post", methods=["POST"])
async def post(request):
return json({"is_camelcase": True, "message": request.json})


@app.route("/empty", methods=["PUT"])
async def empty_response(request):
return response.empty()


if __name__ == "__main__":
app.run(host="0.0.0.0", port=8000)

that all what we need is to instantiate the Camelize class with the sanic() object.

pipenv run python main.py

{"isCamelcase": true, "message": "Hello_world"}

Camelize only deals with keys not values hence “Hello_world” remains the same.

I have already packaged this middleware So no need to create a new middleware if you need you use sanic_camelcase_middleware just use pip

pip install sanic_camelcase_middleware

Writing tests

create a new file tests.py

import json
from humps import camelize
from main import app

def test_get_request():
"""test_get_request function tests get requests for middleware
The purpose of this test is to check if the middleware would work if the request
has no payload in case of "GET" requests.
However the respons should be camilized.
"""
request, response = app.test_client.get("/")
assert response.status == 200
assert response.json == {"isCamelcase": True, "message": "Hello_world"}

Running tests

pipenv run pytest tests.py

Conclusion

If speed and performance is not a big issue for the project you are working on, I would recommend sticking to Flask since it has much more extensions and have been working in production environment for quite a while now. If speed is what you need I would recommend FastAPI since it is even faster that the any other python framework, I wrote this article about it. However if your code is already in Flask, and better performance is required, and there not much options but moving away from Flask, here is where Sanic shines since it is quite close to Flask in terms of structure and naming convention.

Software Developer