ivankorobkov / python-inject

Python dependency injection
Apache License 2.0
670 stars 77 forks source link

Singletons for AsyncIO #105

Open Gr1N opened 1 week ago

Gr1N commented 1 week ago

Hey!

First of all, thank you for the library; I love the simplicity of the design and how it can be easily used in projects.

However, it seems impossible to have singletons for AsyncIO components. Yes, we can have them as providers and async context managers, as it stated in the examples:

@contextlib.asynccontextmanager
async def get_conn_async():
    obj = MockConnection()
    await obj.connect()
    yield obj
    await obj.destroy()

def config(binder):
    binder.bind_to_provider(MockConnection, get_conn_sync)

inject.configure(config)

@inject.autoparams()
def example(conn: MockConnection):
    # Connection and file will be automatically destroyed on exit.
    pass

It can be a good approach for simple cases, but I don't want to open database connections each time I call some function. Of course, because of the nature of AsyncIO, I think you stuck to this design, and probably without something like await inject.wait_shutdown() with manual control, we can't achieve singletons.

Nevertheless, maybe I'm missing something. Is it possible to inject a single database connection without any additional magic?

ivankorobkov commented 3 days ago

Hi!

I think this is application/database specific actually and have little to do with inject. Yet, maybe I am wrong.

ivankorobkov commented 3 days ago

Could you give some examples what you are trying to achieve?

Gr1N commented 3 days ago

Yep, sure.

Imagine we develop a basic web application. In most cases, we need connections to PostgreSQL, Redis, and Kafka (put more of them here). And we may want to inject them at various levels of our application. The good part is that the inject library may help with injection functionality, which is pretty useful.

However, regarding the AsyncIO application, we need to focus on two additional aspects of our connections: how to start and shut them down. The best way to do that is to incorporate such logic into the application lifecycle, start them once on application startup, provide singletons to use in any part of the application, and then gracefully stop them during application shutdown.

With the current implementation of the library, I see only two potential solutions:

I think it would be great to have such logic out of the box to cover the application's lifecycle for AsyncIO components.

ivankorobkov commented 3 days ago

The best way to do that is to incorporate such logic into the application lifecycle, start them once on application startup

Inject is specifically designed as a simple minimalistic DI-framework, not an application framework. You could build your own app with a custom lifecycle using it.

I can use .bind_to_provider(). It can work for simple cases, but it is expensive, for example, for a database connection to open and close them each time I inject a client into a function or method.

You could use a connection pool and get/release a connection each time.