DRY code in middlewares with async/sync support

So, with Django 3.1 out all middlewares needs to be written to support async, so keeping it DRY is important.
I’ve got a bunch of simple util functions like these ones:

def generate_guid() -> str:
    return uuid.uuid4().hex
def validate_guid(original_guid: str) -> bool:
    try:
        return bool(uuid.UUID(original_guid, version=4).hex)
    except ValueError:
        return False

As i see it there is four options:

  1. Use asgiref and call the functions through async_to_sync
    Example:
from asgiref.sync import sync_to_async

async_generate_guid = sync_to_async(generate_guid)
  1. Write the function async and reverse it for the sync middleware:
from asgiref.sync import async_to_sync

async def generate_guid() -> str:
    return uuid.uuid4().hex

sync_generate_guid = async_to_sync(generate_guid)
  1. Use asyncio.run_in_executor on the sync functions through async context.

  2. Just call it? (Blocking?)

Any preferences for functions like these?

On my system, uuid.uuid4().hex runs in less than 0.00005 seconds (roughly 200,000 / second). I also can’t see where it’s performing any IO that would cause it to significantly block.

I’d have no problems running it in a blocking mode.

But that’s generally what you’d be looking for - is the function performing IO? Is it doing anything that could seriously affect the overall throughput? If so, then it’s (possibly) worth making it async-friendly or async-aware. If not, then there’s really nothing to be gained by doing it that way.

Ken

Yeah, that makes sense. :slight_smile: I wasn’t sure of where I should put the bar. In aiohttp you await the .json() for instance. In httpx you don’t.

Ok, I’ll bite - I’m really curious about this.

I’m assuming you’re talking about https://www.python-httpx.org/?

When I look at the docs at https://www.python-httpx.org/async/, all the examples show the requests being "await ed".
Would you mind elaborating on your statement for me?

Ken

Hehe.

Yes, I’m talking about that library. It’s true that you await the request, but never the .json().
I think it’s easier to see if you look at the aiohttp docs. You can see that you have to await to read request data.

As a short comparison between httpx and aiohttp:

import asyncio
import httpx
import aiohttp

async def httpx_example():
    async with httpx.AsyncClient() as client:
        response = await client.get('https://swapi.dev/api/people/1')
        # httpx do not need to await
        json = response.json()  # <--- No await
        print(json)
        
async def aiohttp_example():
    async with aiohttp.ClientSession() as session:
        response = await session.get('https://swapi.dev/api/people/1')
        json = await response.json()  # <--- Await
        print(json)

asyncio.run(httpx_example())
asyncio.run(aiohttp_example())

FYI, I’d put the response in a with-statement too, but was done to make it easy to compare.
aiohttp code should probably look something like this:

import asyncio
import aiohttp

async def aiohttp_example():
    async with aiohttp.ClientSession() as session:
        async with session.get('https://swapi.dev/api/people/1') as response:
            json = await response.json()  # <--- Await
            print(json)
1 Like