Although something like Flask's globally accessible request object is considered a terrible way of writing code (explicit is better than implicit), sometimes it makes sense to use it. For example, while passing a Correlation-ID to track a request's life cycle through your micro-services.



You can memorize the Correlation-ID throughout the lifecycle of a request without explicitly passing it around like juggling balls. This is actually a good approach as the Correlation ID is not a core business logic - just a distraction. We'll see how we can implement such a request-bound "global" context in Sanic, and how to setup a simple Correlation ID implementation.

aiotask-context

This nifty Python module can maintain a distinct context against each asyncio Task. Which means, each request can have an associated context we can use to store and pass around passive details.

$ pip install aiotask-context

from sanic import Sanic
from sanic.response import json

# import aiotask-context
import aiotask_context as context

app = Sanic()

# hook aiotask-context
@app.listener('after_server_start')
async def hook_context(app, loop):
    loop.set_task_factory(context.task_factory)

@app.route("/")
async def test(request):
    return json({"hello": "world"})

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

Now we have a simple Sanic app with the context hooked up.

Implementing Correlation ID Generation and Passing

Now, let's grab the correlation ID if it comes with the request, or generate our own otherwise. Afterwards, we need to save that value to the context for future use in other parts of the code (i.e. logging, requests to other microservices and so on).

from uuid import uuid4
import aiotask_context as context

@app.middleware('request')
async def handle_correlation_id(request):
    cid = request.headers.get('X-Correlation-ID') or str(uuid4())
    context.set('cid', cid)

And throughout the code, if you need the cid or any context value you have set, simply use context.get(key).

And the last step is definitely all about responding with the Correlation ID. We'll be sticking to the middleware for this too. We just need to update the response object from the context.

@app.middleware('response')
async def insert_correlation_id(request, response):
    response.headers["X-Correlation-ID"] = context.get('cid')

Great! Now we don't have to write spaghetti code and get lost in passing CIDs from functions to functions. Cheers!