Tired of waiting for sluggish HTTP requests to complete before your backend code can proceed with other things?
Sanic is an asynchronous web framework in Python, that is built to be fast. In a world where Flask and Django are the most preferred web development options in Python, Sanic is the new kid on the block. It’s a promising alternative that is not only faster but also delivers efficiency, simplicity, and scalability.
In this post, we will learn about asynchronous programming in the context of web applications and about how that makes Sanic so fast. We will also learn how to define routes with Sanic, serve assets, play with requests and responses, and more. Let’s get started!
Use these links to navigate the guide:
- What is Sanic?
- Asynchronous Programming Basics
- Getting started: Sanic Python Tutorial
- Hello World in Sanic
- Routing
- Serving content
- Playing with requests and responses
- Advantages and Drawbacks
- Example Projects
- Wrapping it up
What is Sanic?
Sanic is a server-side web framework written for Python 3.6+ that focuses primarily on speed and performance. It gets its name from a popular Internet meme called Sanic (from Sonic the Hedgehog).
Web frameworks like Sanic allow you to create server-side systems that can serve web-pages, process data, make requests, talk to databases, open sockets and much more without having to worry about underlying implementations like thread management and transfer protocols.
Sanic is, in many ways similar to Flask, in terms of its simplicity and ease of setting up. But what makes it truly stand out is the speed factor. Using Sanic, you can develop super-fast backend systems for your web applications by utilizing asynchronous, non-blocking code mechanisms, thanks to the async/await syntax introduced in Python 3.5.
This difference in speeds might not be evident in small-scale personal projects, where the amount of data transfer might be limited; but as projects scale and become more IO extensive, the impact can be gargantuan.
With over 10k stars on Github, Sanic provides a clean and well-documented API, backed by a growing community of Python developers all over the world. Even though the project is under active development and still in its infancy, it promises to be a viable proponent of the power of asynchronous web systems. Let’s see how.
Asynchronous Programming Basics
What is asynchronous programming?
Asynchronous programming refers to the paradigm of concurrent programming where apart from a single primary application thread, work can be delegated to one or more parallel worker threads. This is referred to as a non-blocking system where overall system speed is not affected by the order execution and where multiple processes can happen at the same time.
Let us understand this with an example –
If you were to create a weather application, you might want to fetch the temperature of a city from one external API and the wind-speed or rain-likelihood from another. In a synchronous setting, this would happen in a sequential manner where the second API request is made only on the successful completion of the first one. This delay can keep adding up as the number of requests increases, resulting in bad user experience. Asynchronous code allows you to implement the above in a non-blocking fashion so that the second request can be initiated before waiting for the first one to complete.
Python introduced support for asynchronous code in Python 3.5 with the async/await syntax. To be clear, Python uses a single-threaded, single-process design and only gives the impression of parallelism, which can be called ‘cooperative multitasking’ (courtesy: realpython.com). Sanic has been developed to utilize this capability of Python, giving it an edge over other, standard web development frameworks.
Getting Started – Sanic Python Tutorial
Let us set up our workbenches, install Sanic and learn how to create a web application using Sanic with an example.
Note: Make sure you have at least version 3.6 of Python before starting. Sanic uses the new async/await syntax, so earlier versions of python won’t work.
Installation
To set up the environment, we’ll create a virtual environment using virtualenv and then install Sanic using Python’s PIP package manager.
Using a virtual environment is always a good practice as it keeps your project dependencies cleanly managed, segregated from the global Python modules.
Installing virtualenv (optional) –
python3 -m pip install virtualenv
Creating a new directory for this tutorial –
mkdir sanic-tutorial
cd sanic-tutorial
Creating a virtual environment, and activating it –
virtualenv .
source bin/activate
Now that our virtual environment is ready, let’s install Sanic.
python3 -m pip install sanic
Let’s create a new file app.py and write some code.
Hello World in Sanic
The ‘Hello World’ equivalent for a web framework would be to get the web server up and running. Let’s write the below code in app.py and run the file.
# app.py
from sanic import Sanic
app = Sanic()
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000, debug=True) # 'debug' param auto-reloads server when code is changed
Output:
We can see the server has been started on the specified port of our localhost. This means everything is set up correctly and we can proceed.
Note: The server logs in the terminal, like in the screenshot above can be very helpful for debugging.
Navigating to the URL at present would raise an error since we haven’t defined any routes for serving any data.
Output:
Let’s go make some routes.
Routing
What is an end-point?
A route (or an end-point) in a backend system is basically a channel for data to flow between the client and the server.
This communication happens usually through an HTTP-based request-response paradigm. Web frameworks like Sanic allow you to create such routes and define their behaviors – their handler functions, in terms of how the request is processed.
Making your own routes
The default home-page path corresponds to ‘/’ route. Routes in Sanic are defined using Python decorators followed by their respective handler functions.
Let’s create a route for our website’s home-page.
from sanic import Sanic, response
app = Sanic(__name__)
@app.route("/")
async def home(request):
return response.text("Hello Sanic")
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000, debug=True)
If you have used Flask before, you’d be able to spot the similarities. The only major difference here is the async function and how the response has to be explicitly defined as a Sanic response object.
Output:
We can also create routes for custom paths, for example – /myhomepage.
@app.route("/myhomepage")
async def home(request):
return response.text("My home page!")
Output:
While creating a route, we can also define the HTTP methods that we want the route to support – GET, POST, DELETE etc.
@app.route("/myurl")
async def myurl(request, methods=[‘GET’, ‘POST’]):
return response.text("My home page!")
Serving content
In the above examples, we returned just a string as a response. There’s much more that these routes can serve – HTML content, JSON, media files etc. For the purpose of this tutorial, we’ll see how we can serve static assets like images or text files, and render some HTML content.
Serving static assets
Static files can be served using Sanic by using the app.static() method which takes as input the route’s URL path and the path to the static file.
I’m going to serve a Sanic image I downloaded from the internet.
from sanic import Sanic, response
app = Sanic(__name__)
app.static('/sanic.jpg', '/PATH/TO/FILE')
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000, debug=True)
Output:
You can do the same for any .txt, .pdf, .json file or any other supported format.
Serving HTML content
This blog post you’re reading on the ScoutAPM website is essentially some HTML content being served off a web server running somewhere on the cloud.
Let’s see how we can serve some HTML content from our dummy website.
from sanic import Sanic, response
app = Sanic(__name__)
@app.route('/')
def handle_request(request):
return response.html('<p>Hello Sanic!</p>')
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000, debug=True)
Output:
We can also serve HTML content from a file on our system by using the response.file object as such –
from sanic import Sanic, response
app = Sanic(__name__)
@app.route('/')
async def index(request):
return await response.file('index.html')
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000, debug=True)
Notice how the await keyword has been used while returning the response. As a result, a coroutine object is returned which is passed to the event loop to be executed later on.
This means that the part of the code that reads and processes the HTML file will be non-blocking in nature and wouldn’t prevent any other processes from running in the time that it takes to process the HTML file. This asynchrony is what gives Sanic it’s edge in speed.
Playing with requests and responses
Let’s see how we can use Sanic to process a request from the client.
I will be using an application called Postman to create dummy requests to the server.
Here, I am making a GET request to the /form route. The payload for the request is a key-value pair: {‘hello’: ‘world’}.
Before we send the above request, let’s create a route for the same in our code. We can access the request arguments from the request object. The payload can be fetched using request.args, which returns a dictionary containing the data.
@app.route('/form')
async def form(request):
print(request.args)
return response.text("Reading request")
Output:
You can also similarly access other request properties like the headers, body, URL, path, method etc. using the request object.
Some of the commonly used request attributes are –
- request.headers: Request headers
- request.args: To obtain query string variables
- request.files: To obtain the sent files
- request.json: To obtain JSON body
- request.method: HTTP method used by the request
Information can also be passed to the server through URL request parameters –
A request to the /items/table route can be captured as such –
@app.route('/items/<item>')
async def item_handler(request, item):
print('Item - {}'.format(item))
Output:
Based on the data received, we can program the response of the route.
Before we wrap up the tutorial, let’s use what we learned to create a dummy welcome-page route handler for a user based on the request received.
For a GET request to /welcome?user=sanic, our app.py file can have a handler function like so-
# app.py
from sanic import Sanic, response
app = Sanic(__name__)
@app.route('/welcome')
async def welcome_handler(request):
user = request.args['user'][0]
html = '<h2>Welcome ' + user + '!</h2>'
return response.html(html)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000, debug=True)
Here, we fetch the user information from the request object, wrap it in HTML and send it as a response back to the client.
Output:
Advantages and drawbacks
Advantages of Sanic include –
- Light-weight, flexible
- Offers support for async/wait
- Fast and scalable
Drawbacks of Sanic include –
- It doesn’t come with an inbuilt templating engine.
- It is still an upcoming framework in some sense, which means library extensions – applications that extend the functionality of Sanic, might be less compared to Flask and Django.
Example projects
Here are some of the Sanic projects in production that you can refer to –
Here are some Sanic extensions developed by the community-
- Sanic-GraphQL – To integrate GraphQL with Sanic
- Sanic_CRUD – REST API framework for creating a CRUD API
- Pytest-Sanic – Pytest plugin for Sanic
Wrapping it up
In this post, we learned about Sanic, a web server framework that’s written to go fast, allowing developers to make the most of the async/await support introduced in Python 3.5. We learned about asynchronous programming and played around with the basics of Sanic. We defined routes, served assets, rendered HTML content, inspected the request object and created a dynamic dummy welcome page.
Now that you’ve got the basics covered, try making something from what you’ve learned – a portfolio website, an API of your favorite TV show’s quotes, a random cat gif generator API, anything. Feel free to refer to this tutorial or the Sanic documentation (https://sanic.readthedocs.io) if you get stuck.
Now go build something fast!