Custom ResourceFactory provider help
Hi! I'm trying to handle SQLAlchemy sessions as a resource provider instead of using FastAPI's built-in Depends.
Let me share some code:
The database class:
class Database:
"""Class to handle database connections and sessions"""
def __init__(self, db_url: str, **kwargs):
"""Initialize the Database class with a database URL and optional keyword arguments"""
self.db_url = db_url
self.engine = create_engine(self.db_url, **kwargs, echo=settings.DEBUG_MODE, connect_args={"check_same_thread": False}, pool_size=0)
self.local_session = sessionmaker(autocommit=False, autoflush=False, bind=self.engine)
@contextmanager
def session(self):
"""Context manager to handle sessions"""
session: Session = self.local_session()
try:
yield session
except Exception as e:
session.rollback()
finally:
session.close()
The main container where I create these resources:
def get_db_session(session_factory):
with session_factory() as db:
yield db
class AppContainer(containers.DeclarativeContainer):
"""Container to serve all the containers related to the app"""
# ..... other injections
# Setup container for talent services
db = providers.Factory(Database, db_url=settings.DB_URL)
db_sess = providers.Resource(get_db_session, session_factory=db.provided.session)
user_container = providers.Container(
UserContainer, # and this container has "UserService" that also uses the db session dependency
db_sess=db_sess,
)
# ....
So, I inject the session into the services directly (or in the future in one repository). I don't like injecting the session into the routers directly that's why I prefer not using the FastAPI Depends.
The code provided works as expected, it creates 1 session per request and then closes it, the thing is that this doesn't work in a concurrent environment, I made some local testing with locust, and once I start having many requests executing with multi-threads (I'm using sync endpoints), the sessions don't work as expected, it seems that the same session is being used in more than 1 request at the same time, so I guess this is something that we could solve if we had something like FactoryResource instead of just a Resource that works as a Singleton. I tried to implement this myself by creating a CustomProvider but I couldn't make it, so if anyone has any advice I'd appreciate it.
P.S: I checked all the related issues regarding FastAPI and SQLAlchemy sessions in this repo and tried some approaches but none of them worked correctly
@agusmdev you dont want a session/connection per request right? I thought that what you want is that connections are reused
@agusmdev wouldnt a possible solution be to create the session before a request, with middleware, and close it afterrwards
@agusmdev you dont want a session/connection per request right? I thought that what you want is that connections are reused
@theobouwman Mmm I think that I do want 1 session per request. I'm using FastAPI and I'd like to emulate the same injection that you can do with FastAPI built-in Depends but given that I want to inject the session in a service and not in a router I can't use the FastAPI built-in injector.
@agusmdev wouldnt a possible solution be to create the session before a request, with middleware, and close it afterrwards
Yes, it's possible, but the session per request is working correctly with the implementation I have, the issue appears when you have a bunch of requests at the same time, the sessions get reused by the requests because the implementation of Resource is like a Singleton and not like a Factory (or that's what I guess that's happening here)
@agusmdev okay I get it. I have the issue that with this implementation (https://github.com/ets-labs/python-dependency-injector/blob/master/examples/miniapps/fastapi-sqlalchemy/webapp/database.py) each query a new session is created, which is not what we want because this takes time as you can see in the screenshot.
Yes, it's possible, but the session per request is working correctly with the implementation I have, the issue appears when you have a bunch of requests at the same time, the sessions get reused by the requests because the implementation of
Resourceis like aSingletonand not like aFactory(or that's what I guess that's happening here)
What complications could arise when using this?
Yes, it's possible, but the session per request is working correctly with the implementation I have, the issue appears when you have a bunch of requests at the same time, the sessions get reused by the requests because the implementation of
Resourceis like aSingletonand not like aFactory(or that's what I guess that's happening here)
How do you test this?