visit
Views are key components of applications built-in with the Django framework. At their very simplest, views are Python functions or classes that take a web request and produce a web response. Prior to Django 3.1, views had to run with Python threads. Now, views can run in an asynchronous event loop without Python threads. This means Python’s
asyncio library can be used inside of Django views.
We can cut down on that time frame significantly and improve the situation overall by using Pythons concurrent.futures library.This makes it possible to make the four API calls in the previous example concurrently meaning the view could take roughly one second in total if using four workers with the ThreadPoolExecutor. By all accounts, the practice cuts down on the time needed and improves the calls.
That’s important in a world where seconds matter and making someone
wait around for an application to load can cause frustration.
def get_river_flow_and_height(site_id):
"""
Synchronous method to get river data from USGS
"""
response = requests.get(f'//waterservices.usgs.gov/nwis/iv/?format=json&sites={site_id}¶meterCd=00060,00065&siteStatus=all')
data = parse_flow_and_height_from_json(response.json())
return data
def dashboard_v1(request):
"""
Synchronous view that loads data one at a time
"""
start_time = time.time()
river_data = []
for site_id in SITES.keys():
river_data.append((SITES[site_id], get_river_flow_and_height(site_id)))
return render(request, 'rivers/dashboard.html', {
'river_data': river_data,
'version': 'v1',
'load_time': time.time() - start_time,
})
Result:
def dashboard_v2(request):
"""
Concurrent view that loads some data simultaneously
"""
start_time = time.time()
river_data = []
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
for site_id, data in zip(SITES.keys(), executor.map(get_river_flow_and_height, SITES.keys())):
river_data.append((SITES[site_id], data))
return render(request, 'rivers/dashboard.html', {
'river_data': river_data,
'version': 'v2',
'load_time': time.time() - start_time,
})
Result:
async def get_river_flow_and_height_async(site_id):
"""
Asynchronous method to get river data from USGS
"""
async with httpx.AsyncClient() as client:
response = await client.get(f'//waterservices.usgs.gov/nwis/iv/?format=json&sites={site_id}¶meterCd=00060,00065&siteStatus=all')
data = parse_flow_and_height_from_json(response.json())
return data
async def dashboard_v3(request):
"""
Asynchronous view that loads data using asyncio
"""
start_time = time.time()
river_data = []
datas = await asyncio.gather(*[get_river_flow_and_height_async(site_id) for site_id in SITES.keys()])
for site_id, data in zip(SITES.keys(), datas):
river_data.append((SITES[site_id], data))
return render(request, 'rivers/dashboard.html', {
'river_data': river_data,
'version': 'v3',
'load_time': time.time() - start_time,
})
Result:
This example shows pretty clearly how asynchronous views can be leveraged to drastically improve performance.This is just a small example of the performance improvements we see on a daily basis with the projects we work on at .View the full project on GitLab:
Previously published at