Skip to content
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

Custom ResourceFactory provider help #760

Open
agusmdev opened this issue Nov 1, 2023 · 6 comments
Open

Custom ResourceFactory provider help #760

agusmdev opened this issue Nov 1, 2023 · 6 comments

Comments

@agusmdev
Copy link

agusmdev commented Nov 1, 2023

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

@theobouwman
Copy link

@agusmdev you dont want a session/connection per request right? I thought that what you want is that connections are reused

@theobouwman
Copy link

@agusmdev wouldnt a possible solution be to create the session before a request, with middleware, and close it afterrwards

@agusmdev
Copy link
Author

agusmdev commented Nov 8, 2023

@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)

@theobouwman
Copy link

@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.

Screenshot 2023-11-08 at 20 26 36

@theobouwman
Copy link

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)

What complications could arise when using this?

@theobouwman
Copy link

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)

How do you test this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants