Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to use fastapi with Django ORM ? #716

Closed
vitalik opened this issue Nov 19, 2019 · 15 comments
Closed

How to use fastapi with Django ORM ? #716

vitalik opened this issue Nov 19, 2019 · 15 comments
Labels
question Question or problem question-migrate

Comments

@vitalik
Copy link
Contributor

vitalik commented Nov 19, 2019

Description

I'm using Django ORM + Mysql with fastapi
all works fine

but at some point when application have no requests for some period of time I'm getting

2006 mysql server gone away

Most likely Django does some connection checks or closing somehere in the request flow..

(I tried to play with CONN_MAX_AGE and close_old_connections in middleware - no luck)

any idea how to do it with fastapi ?

@vitalik vitalik added the question Question or problem label Nov 19, 2019
@prostomarkeloff
Copy link
Contributor

Try use asynchronous alternatives of Django ORM. For example, try it: https://github.com/tortoise/tortoise-orm

@vitalik
Copy link
Contributor Author

vitalik commented Nov 19, 2019

@prostomarkeloff thank you
but there is a requirement to re-use a lot of Django code.

@dmontagu
Copy link
Collaborator

dmontagu commented Nov 19, 2019

I definitely think getting fastapi to work with the Django ORM is a reasonable goal, and I've heard of multiple people doing this.


I think there was a similar issue that came up recently related to connection staleness using asyncpg directly. (@prostomarkeloff note this implies that using an async ORM won't necessarily prevent this issue!)

If you want to prevent connection staleness issues, the easiest way is probably to do a "pre-ping" where you try to run an extremely cheap query (typically "select 1") using the ORM's db connection inside a middleware at the beginning of every request (or at least every request that makes use of the ORM). If it fails, you would then get a new connection (presumably the ORM would handle this for you, and you would just handle the exception in the middleware, but the right way to handle this may require some special handling specific to the Django ORM).

Note: despite the additional overhead, when I first saw this I was surprised to learn this is not considered a bad practice; it is discussed for sqlalchemy here: https://docs.sqlalchemy.org/en/13/core/pooling.html#dealing-with-disconnects (under the "pessimistic" handling section).

Given the amount of usage implied by this statement:

at some point when application have no requests for some period of time

(i.e., presumably you aren't pushing the limits of what your hardware can handle) my guess is that wasting the handful of milliseconds it would take to get the response to select 1 is worth it to prevent the occasional stale connection. (But that's obviously dependent on the priorities for your application.)

I am not familiar enough with django to be able to advise how to accomplish this, but I think it should be possible.


I suspect there is also a way to get the Django ORM to gracefully handle connection issues in an optimistic way, but again, I don't know enough about the Django ORM to advise.

@tiangolo
Copy link
Owner

Thanks for the discussion here!

@vitalik , does @dmontagu 's answer solve your problem? Did you solve it in any other way?

@vitalik
Copy link
Contributor Author

vitalik commented Feb 12, 2020

@tiangolo
Unfortunately - no

the goal is that connection should be checked / pinged before using ORM and closed if not responding

BUT the problem is that Django persist connection object to mysql inside threading.local

on the other hand starlette/fastapi run each operation in threadpool executor

meaning that middleware can run in one thread, but actual operation with ORM calls in other thread
which makes it impossible to check connection on some high level like middleware :(

@dmontagu
Copy link
Collaborator

@vitalik For what it's worth, you should be able to perform middleware-like operations inside the threadpool thread via a custom route handler: https://fastapi.tiangolo.com/advanced/custom-request-and-route/#custom-apiroute-class-in-a-router

Though admittedly this is less ergonomic than one would hope for.

Another approach might be to use the middleware to put a Django connection into a contextvar, which is passed into the threadpool executor's call. But I suspect that threadlocals are likely to cause problems if used in a concurrent context like this.

Hopefully as Django becomes more ASGI compatible they refactor into using contextvars rather than thread locals anyway. I'm not sure if there has been any discussion on this topic from the django devs but that might be a good topic to search for for ideas about how to work around this.

@pkuphy
Copy link

pkuphy commented Mar 4, 2020

I use this decorator to close stale connections, don't know if it helps in the async world.

def ensure_fresh_db_connection(f):
    @wraps(f)
    def wrapper(*args, **kwargs):
        django.db.close_old_connections()
        result = f(*args, **kwargs)
        return result
    return wrapper

@tiangolo
Copy link
Owner

I'm not currently using Django, but some people have been doing experiments mixing Django and FastAPI, e.g. https://www.stavros.io/posts/fastapi-with-django/

Maybe that can help you with your use case.

@github-actions
Copy link
Contributor

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

@koffisani
Copy link

This can help somebody https://www.stavros.io/posts/fastapi-with-django/

@kigawas
Copy link

kigawas commented Oct 12, 2020

Hey guys, I also wrote an article about how to integrate Django ORM into FastAPI.

You might also what to check this and see how I did it:

https://kigawas.me/posts/integrate-fastapi-and-django-orm/

Corresponding github repo:

https://github.com/kigawas/fastapi-django

@sapristi
Copy link

        django.db.close_old_connections()

I was getting django.db.utils.InterfaceError: connection already closed errors when using django ORM from inside fastAPI views (using postgres as a db). I can confirm running this in the views solved the issue for me !

@bryanhelmig
Copy link

bryanhelmig commented Feb 2, 2022

For anyone trying to do this, https://gist.github.com/bryanhelmig/6fb091f23c1a4b7462dddce51cfaa1ca outlines an example asgi.py which makes close_old_connections more convenient via middlewares.

It also showcases a fun difference between how Starlette's run_in_threadpool() and Django's sync_to_async() behaves, which duplicates connections (boo!) but at least the dead connections get cleaned up in both instances.

Update: there is an improved smart_sync_to_async() in the gist which fixes the duplicate connections issue, but its a little hacky!

@vitalik
Copy link
Contributor Author

vitalik commented Feb 3, 2022

Hi @bryanhelmig

Wow great work!
I tried to go that rabbit hole, but at some point said "screw it" and made django ninja instead :D

@bryanhelmig
Copy link

@vitalik django-ninja is very cool, I commend you on the work there! It's definitely a slog to reimplement so many of the patterns from FastAPI. I suspect it would be a boon for the Python community to have more standardized "sync to async" or "async to sync" implementations that can help libraries co-mingle so developers can pick and chose, or adopt new libraries in a piecemeal fashion. That is a tall order though, already so much great work being done! 😅

@tiangolo tiangolo changed the title [QUESTION] How to use fastapi with Django ORM ? How to use fastapi with Django ORM ? Feb 24, 2023
@tiangolo tiangolo reopened this Feb 28, 2023
@github-actions github-actions bot removed the answered label Feb 28, 2023
Repository owner locked and limited conversation to collaborators Feb 28, 2023
@tiangolo tiangolo converted this issue into discussion #8009 Feb 28, 2023

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
question Question or problem question-migrate
Projects
None yet
Development

No branches or pull requests

9 participants