ivankorobkov / python-inject

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

Usage of python-inject in a web app: How to mix (static) "once in a lifetime" bindings and dynamic `bind_to_provider`? #84

Closed hf-kklein closed 2 years ago

hf-kklein commented 2 years ago

Hi, I'm a bit lost when it comes to the difference between configure, install and configure_once. In a web app I want to inject some dependencies during the startup of the application (once for the entire lifetime of the app). Other dependencies I'd like to inject using bind_to_provider but the provider is only just created at runtime because it's based on user inputs.

How can I mix both:

  1. bindings at startup
  2. bind_to_provider at a completely different place in the code

Do I have to clear_and_configure all bindings including those that have already been bound during startup evertime I bind something (dynamically) to the provider? A MWE would be greatly appreciated :)

ivankorobkov commented 2 years ago

Hi,

Everything is pretty simple:

In a web app I want to inject some dependencies during the startup of the application (once for the entire lifetime of the app). Other dependencies I'd like to inject using bind_to_provider but the provider is only just created at runtime because it's based on user inputs.

Configuration is static and should be specified at one point. Configuration is roughly a map between types and various providers.

def config(binder):
  binder.bind(Database, PgDatabase('localhost:5432')) # will inject the same instance everywhere
  binder.bind_to_provider(Cache, get_cache) # will call get_cache to get an instance every time

How can I mix both:

Everything should be specified during startup. You cannot change injector configuration after configure call.

hf-kklein commented 2 years ago

thanks for this super fast and helpful answer :)

hf-kklein commented 2 years ago

To anyone having the same issue. The approach I used in the end is a ContextVar (which is imho way easier to use than the threading.local() described in the README.

I call the following code on startup:

class MyLocalData:
    # something dynamic that changes e.g. every request

class MyStaticThing:
    # something of which I want to get the same instance every time

current_local_data: ContextVar[MyLocalData] = ContextVar("current_local_data")

def get_current_local_data() -> MyLocalData:
    return current_local_data.get()

my_static_singleton = MyStaticThing()

inject.configure(lambda binder: binder
    .bind(MyStaticThing, my_static_singleton) # <-- same for all calls
    .bind_to_provider(MyLocalData, get_current_local_data)) # <-- changes with the context var

then, in my web application request handler I only need to set the context var:

def my_request_handler():
    local_data : MyLocalData = ... # something from the request body... or whatever
    current_local_data.set(local_data)
    # do the thing that uses python-inject