Is there some request_context like app_context?

Hi!

I’m writing some code (tests, celery, and other) and I’d like to wrap my code in a context.

Generally, I would go with a app_context, to have the application available inside that code, but in my case, I initiate the database session in the request context, not the app context (for the simple reason that I handle the commit/rollback actions once the request have finished, depending on the response (error or not)).

Because of that, the database is not availabe if I run my code inside an app_context, but it is when I run it inside a request_context.

Is this something possible on Sanic ?

Thank you :slight_smile:

Wouldn’t you do this by wrapping a route in middleware and set up the connection in the middleware?

Yes, but what I’m trying to do, is have the same “context” as a request (with Middleware executed) for running some scripts (not a real request). This is to execute the middleware between that script, including connecting to the database. I’m not really looking for the request per se, but to execute all the middleware instances in between.

Does it makes sense?

(For now, I’m calling the db wrapper manually, instead of relying on the middleware, but it is not scalable).

Sounds like an architectural issue that you can solve by creating a service to handle this that can be called independently, or inside your middleware.

@app.on_request
async def setup_db_connection(request: Request):
    request.ctx.db = DBConnectionWrapper(request.app.config.DB_CREDS)

That’s an interesting take that leads me to another question related to this one.

Currently, my db access is wrapped in a async with db: and the request handling is done inside this “with”.

The idea for this is to have the same access to the db accross the request, and managing the finalization of the request when exiting the “with” (rollback in case of exception, or commit otherwise).

I tried using the middleware for that, with “on_request” and “on_response”, but they are not called consecutively (on_request > handler > on_response), which causes an inequal number of call between on_request and on_response (I can have more on_response than on_request when requesting multiple resources, probably also linked to websockets).

My question is:

Is it possible to wrap the handling request using “with” (or another mechanism that ensure that the request handler is wrapped inside the same “with”) using middleware?

Thanks for the help!

(As of now, I’ve rewritten the Route class and call the “async with db:” before calling the handler)

You would basically do whatever logic is happening inside the with statement broken up between the middleware. You cannot wrap the handler anywhere…

… unless you created a decorator.

from inspect import isawaitable
from functools import wraps

def transaction(func):
    def decorator(f):
        @wraps(f)
        async def decorated_function(request, *args, **kwargs):

            async with request.app.db.transaction() as tx:
                request.ctx.transaction = tx
                response = f(request, *args, **kwargs)
                if isawaitable(response):
                    response = await response

            return response

        return decorated_function

    return decorator(func)


@app.post("/")
@transaction
async def something(request):
    ...

Using a template

Interesting.

The transaction decorator is a good approach. It allows you to start a DB session only when needed: Endpoints that don’t need a DB connection won’t have one, reducing the load :slight_smile:

Thanks!

1 Like