python-dependency-injector icon indicating copy to clipboard operation
python-dependency-injector copied to clipboard

Sington provider with setup and teardown methods?

Open rollo-b2c2 opened this issue 4 years ago • 5 comments

How can I create a Singleton object based on a generator?

For example


def session_factory(connection):
   with connection() as conn:
        session = Session(bind=conn)
        yield session
        try:
            session.commit()
        except:
            session.rollback()
        finally:
            session.close()


class Container(containers.DeclarativeContainer):
    session = providers.ThreadLocalSingleton(session_factory, connection=...)

rollo-b2c2 avatar Jul 22 '21 10:07 rollo-b2c2

Hi @rollo-b2c2 , take a look on https://python-dependency-injector.ets-labs.org/providers/resource.html

rmk135 avatar Jul 22 '21 21:07 rmk135

The problem is with a resource is that it's a global, once it's instantiated it's there until someone shuts it down.

rollo-b2c2 avatar Jul 23 '21 12:07 rollo-b2c2

How is that different behaviour from your description in the issue?

EdwardBlair avatar Aug 15 '21 01:08 EdwardBlair

I'm looking at the same situation when trying to open DB session per FastAPI request by using Resource provider.

As a base I've used example from docs and added resource with:

def get_database_session(database_session_factory: Database.session):
    with database_session_factory() as database_session:
        yield database_session
        
database_session = Resource(
        get_database_session,
        database_session_factory=database.provided.session,
    )

Issue with this code is that database session is only opened once (on first request) then object is stored globally and server for every consecutive request. I'm also noticing that session is never closed, I guess because generator is not called for the 2nd time.

It seems like some hybrid between Factory (executes on every request) and Resource (supports yield and doesn't need a class) provider would do the job? Was looking through docs and fiddling with it but it seems like there is no appropriate provider available, did I miss any?

I think this issue may also be related to https://github.com/ets-labs/python-dependency-injector/issues/495.

tad3j avatar Mar 22 '22 10:03 tad3j

I'm trying to do the same thing but still struggling.

It works if we inject the Resource in the route, using the Closing marker.

For example:

# containers.py
def get_session(session_factory):
    with session_factory() as session:
        logging.warning("Started session")
        yield session
    logging.warning("Ended session")

class Container(containers.DeclarativeContainer):
    db = providers.Singleton(Database, db_url=config.db.url)
    session = providers.Resource(get_session, session_factory=db.provided.session)

    my_repository = providers.Factory(WorkflowRepository, session=session)
    my_service = providers.Factory(MyService, my_repository=my_repository)


# Flask route
@bp.route("/", methods=["GET"])
@inject
def more(db_session: Session = Closing[Provide[Container.session]], my_service: MyService = Provide[Container.my_service]):
    return my_service.execute("something")

I'm not sure if it's working the way I imagine it is, but it seems to work.

lmtani avatar Apr 01 '22 19:04 lmtani