encode/apistar

Session scope dependencies

thedrow opened this issue · 6 comments

I have a component which initializes a connection pool.
I'd like to inject it through the constructor of other components so that the connection pool will only initialize itself once for all components.

You might have to initialize your connection pool component first, then explicitly pass the resolved connection pool to the other components, if it really has to be provided as a part of the constructor.

class ConnectionPoolComponent:
    connection_pool = None

    def __init__(self):
        # do connection_pool init

    def resolve(self) -> ConnectionPool:
        return self.connection_pool

class OtherThingComponent:
    def __init__(self, connection_pool):
        self.connection_pool = connection_pool

    def resolve(self) -> OtherThing:
        return self.get_other_thing()

# in app.py
connection_pool_component = ConnectionPoolComponent()
components = [
    connection_pool_component,
    OtherThingComponent(connection_pool_component.resolve()),
]
App(routes=routes, components=components)

However, in the spirit of how components might better work, perhaps just parameter resolution / injector do it's thing:

class OtherThingComponent:

    def resolve(self, connection_pool: ConnectionPool) -> OtherThing:
        return self.get_other_thing(connection_pool)

# in app.py
components = [
    ConnectionPoolComponent(),
    OtherThingComponent(),
]
App(routes=routes, components=components)

Then your only concern would be to ensure that the ConnectionPoolComponent service up the same ConnectionPool instance per session, or whatever your requirement was.

What about declaring the pool as a class parameter, that is, only one instance of the pool for all instances of the Component? Similar to @rhelms proposes:

class ConnectionPoolComponent:
    connection_pool = None

    def __init__(self):
        # do connection_pool init
        if self.connection_pool is None:
            self.connection_pool = init_pool(**whatever)

    def resolve(self) -> ConnectionPool:
        return self.connection_pool

This keeps just the same connection pool for all instances of the component class.

I am fully aware of the workarounds and I am using them. They are not good enough for my usecase.
Also, resolving these dependencies every time is a waste.

What's the issue with doing what you want to do? I.e initialize the connection pool on app start up and pass this to the other components. Your use case seems pretty cut and dry, if you don't actually want to use the apistar injector.

Injecting in the initializer __init__ not seeming the best idea at a first glance, anyway I've used components injected as annotations in resolve method... and it works. Don't know if that gives you more peace of mind 😅

ConnectionPool = NewType("ConnectionPool")

class ConnectionPoolComponent:
    connection_pool = None

    def __init__(self):
        # do connection_pool init
        if self.connection_pool is None:
            self.connection_pool = init_pool(**whatever)

    def can_handler_parameter(self, parameter: inspect.Parameter):
             return parameter.annotation is ConnectionPool

    def resolve(self) -> ConnectionPool:
        return self.connection_pool

 class AnotherComponent(Component):
         def resolve(self, connection: ConnectionPool):
                return connection.fetch("SELECT 0")

Closing this off given that 0.6 is moving to a framework-agnostic suite of API tools, and will no longer include the server. See https://discuss.apistar.org/t/api-star-as-a-framework-independant-tool/614 and #624.