Fastapi: Middleware for Google NDB

Created on 9 Feb 2020  路  6Comments  路  Source: tiangolo/fastapi

I am trying to create a middleware for Google NDB. It requires the use of an context / the wrapping of the actual code within a "with" statement. It is recommended to create a single context for each HTTP Request. E.g.:

app = FastAPI()
client = ndb.Client()

class Contact(ndb.Model):
    name = ndb.StringProperty()
    phone = ndb.StringProperty()
    email = ndb.StringProperty()

@app.get("/create")
def create():
    with client.context():
        contact1 = Contact(name="John Smith",
                           phone="555 617 8993",
                           email="[email protected]")
        contact1.put()

I tried to implement the "with-wrapping" within a middleware:

@app.middleware("http")
async def add_datastore(request: Request, call_next):
    with client.context():
        response = await call_next(request)
        return response

@app.get("/create_no_ctx")
def create_no_ctx():
    contact1 = Contact(name="John Smith",
                       phone="555 617 8993",
                       email="[email protected]")
    contact1.put()

This middleware has no effect. Calling the create_no_ctx route yields a NDB-Exception that no context was set. Is is possible to create a middleware that wraps all route functions in a with statement with the context?

question

All 6 comments

Google NDB is using threading.local: https://github.com/googleapis/python-ndb/blob/master/google/cloud/ndb/context.py#L37

Please refer to https://fastapi.tiangolo.com/advanced/sql-databases-peewee/#make-peewee-async-compatible-peeweeconnectionstate

Checking form @phy25's comment, it seems that they used to rely on thread locals (that would make this very cumbersome), but it seems they are aware of that and claim to have fixed it for modern Python: https://github.com/googleapis/python-ndb/blob/master/MIGRATION_NOTES.md#bootstrapping

But if they implemented correctly what they say, I think your code "should" work...

Here's something else to try, using a dependency with yield instead of a middleware (that should be more appropriate):

from fastapi import FastAPI, Depends
from google.cloud import ndb


app = FastAPI()
client = ndb.Client()

class Contact(ndb.Model):
    name = ndb.StringProperty()
    phone = ndb.StringProperty()
    email = ndb.StringProperty()


async def create_context():
    with client.context():
        yield


@app.get("/create_no_ctx", dependencies=[Depends(create_context)])
def create_no_ctx():
    contact1 = Contact(name="John Smith",
                       phone="555 617 8993",
                       email="[email protected]")
    contact1.put()

The most difficult part is that they are not using contextvars, but some Google-internal implementation that I don't really know how it works: https://github.com/googleapis/python-ndb/blob/master/google/cloud/ndb/_eventloop.py it seems to be more related to gRPC than to async Python.

I think this is something valid to ask them in their issue tracker: https://github.com/googleapis/python-ndb/issues

If the current implementation is effectively still based on thread locals, that would make their library incompatible with modern Python using async stuff (not just FastAPI but everything that uses async and await). So I think it should be a valid feature request/bug. Also, the fix on their side is probably quite simple, they can more or less replace threading.local with contextvars quite easily, still being compatible with threaded environments but also compatible with event-loop async-based environments: https://www.python.org/dev/peps/pep-0567/#converting-code-that-uses-threading-local

Opps this is a won't fix on their end because of possible alternatives: https://github.com/googleapis/python-ndb/issues/289

Thanks for the pointer @phy25 !

So, in short, from that issue, Google NDB says that you should use:

Google Cloud Firestore (in Datastore Mode using google.cloud.datastore, or native mode using google.cloud.firestore).

Assuming the original issue was solved, it will be automatically closed now. But feel free to add more comments or create new issues.

I created a pull request with the fix according to https://www.python.org/dev/peps/pep-0567/#converting-code-that-uses-threading-local
It really works fine and I hope it will be merged

Was this page helpful?
0 / 5 - 0 ratings