FastAPI: the Python approach to high performance REST APIs
Python is a hot topic these days. It has become the second most used programming language after Javascript. That silver medal is mainly due to its heavy use in AI and (big) data analysis. But while AI and data analysis are fairly new domains, Python has already existed for more than 30 years. It was first released on February 20, 1991.
Python owes much of its development and growth to Google, which has been an avid user of the language. They even employed the Dutchman Guido Van Rossum (Python’s founder) for a long time.
“Python if we can, C if we must”, has been a slogan at Google for numerous years. The reason behind that is fairly obvious: the advantages of Python are crystal clear. It’s easy to learn and has clean and short syntax. That means its code is short and readable, and quickly written. Saving time writing code is one of the biggest advantages of Python.
What is Python used for?
As we mentioned above, Python is heavily used in AI and data science. Additionally, it’s also the better choice in DevOps and task automation. On the other hand, web development is a domain it’s taken Python some considerable time to become an obvious option. The main reason for this is probably because there’s a lot of competition in this area from languages like Java, PHP, Dotnet and NodeJS (= Javascript framework), with PHP and NodeJS being uniquely developed for websites. There’s also another important reason, which we will discuss later in this article.
Python is an interpreted language
Just like JavaScript, Python is an interpreted language. That means Python isn’t directly usable by a computer, but that it needs another program to execute the code. The advantage of this approach is that there’s no need for compilation, which saves time. However, this also comes with a downside: an interpreted language is a lot slower than a compiled language.
There are Python implementations like Cython and Jython, which convert your Python code to bytecode or C extensions. They are outside the scope of this article.
Although Python and Javascript are interpreted languages, Python is (was) in most cases the slowest. One of the reasons for this is that NodeJS was built for the web, has advanced multithreading abilities and is event driven. Python on the other hand is as old as the Internet (first released in 1991) and had to serve different purposes. Multithreading is not built into Python, but it is available in its toolbox.
Speeding up Python with concurrency
When developing websites or APIs in Python, there are a lot of frameworks to pick from. The most popular frameworks are Flask and Django. However, if you only need an API for your fancy React or Angular website, there’s a much better option: FastAPI. Currently at version 0.70.0, FastAPI is rapidly climbing up the popularity ranks. One of the reasons is that it does what it says: it’s fast. Not because it's lightweight, but because it has out-of-the-box support for concurrency (asynchronous functions, requires Python 3.6+).
Concurrency is the element speeding everything up. It’s all about optimizing CPU-bound or I/O-bound tasks, with the latter slowing your code down the most. For example, if your API depends on another API to collect some additional data, the HTTP request will take a considerably large amount of time (in comparison with CPU tasks). If you have multiple calls, that could slow down your code dramatically.
Let’s look at an example. The script below will download 50 websites without concurrency (synchronous).
import requests
import time
def download(url, session):
with session.get(url) as response:
print(f"Read {len(response.content)} from {url}")
def download_all(sites):
with requests.Session() as session:
for url in sites:
download(url, session)
if __name__ == "__main__":
sites = [
"https://www.python.org",
"https://www.jython.org",
"https://pypi.org/",
"https://fastapi.tiangolo.com/",
"https://flask.palletsprojects.com/en/2.0.x/"
] * 10
start_time = time.time()
download_all(sites)
duration = time.time() - start_time
print(f"Downloaded {len(sites)} in {duration} seconds")
If we execute this script, this is the result:
… output skipped …
Read 41381 from https://flask.palletsprojects.com/en/2.0.x/
Downloaded 50 sites in 5.598579168319702 seconds
5 seconds to download 50 websites is not bad, but we can improve this dramatically.
To speed things up, we can choose out of two ways Python can handle concurrency: threading and asyncio. Threading has been introduced in Python 3.2 and works very well. There are a few downsides though. Threads can interact in ways that are subtle and hard to detect. You’ll need to experiment with the number of threads, as that number varies depending on what you want to achieve. An even better way to speed things up is asyncio, introduced in Python 3.6. Asyncio gives you more control and is easier to implement than threading. The same code rewritten to use asyncio would look like this:
import asyncio
import time
import aiohttp
async def download(session, url):
async with session.get(url) as response:
print("Read {0} from {1}".format(response.content_length, url))
async def download_all(sites):
async with aiohttp.ClientSession() as session:
tasks = []
for url in sites:
task = asyncio.ensure_future(download(session, url))
tasks.append(task)
await asyncio.gather(*tasks, return_exceptions=True)
if __name__ == "__main__":
sites = [
"https://www.python.org",
"https://www.jython.org",
"https://pypi.org/",
"https://fastapi.tiangolo.com/",
"https://flask.palletsprojects.com/en/2.0.x/"
] * 10
start_time = time.time()
asyncio.get_event_loop().run_until_complete(download_all(sites))
duration = time.time() - start_time
print(f"Downloaded {len(sites)} sites in {duration} seconds")
If we execute this script, this is the result:
… output skipped …
Read 41381 from https://flask.palletsprojects.com/en/2.0.x/
Downloaded 50 sites in 1.0634150505065918 seconds
The same 50 websites are downloaded in 1 second, which is 5 times faster than the synchronous code!
In benchmarks with NodeJS, FastAPI is faster in almost all cases. Combine this with the development speed of Python, the tons of Python libraries available on the web and the built-in OpenAPI (Swagger) documentation and full-featured testing system and the choice is evident.
Want to speed up your Python development? Contact us to see how we can help!
Fun fact: there’s even Python on Mars
Did you know NASA used Python on the Perseverance Mars mission? The moment of landing itself was recorded by 5 cameras placed in different parts of the probe. Python scripts were used to process the images and transfer them to the flight control center. If NASA uses a programming language, it's probably trustworthy. 😉
P.S. We're organizing a Python Bootcamp event to help you get specialized in Python in just 3 days!