Tinche / incant

https://incant.threeofwands.com
Apache License 2.0
65 stars 3 forks source link

How to integrate a notion of scopes #14

Open jriddy opened 7 months ago

jriddy commented 7 months ago

I'm looking at this project and I like the relative simplicity of it compared to a lot of DI libs for python. But I'm wracking my head trying to figure out how I'd introduce dependency scope. That is...caching what was returned for a factory through multiple invocations on the same incantor object.

To me, this would be necessary to use incantor to inject longer-lived things like a database connection or a requests session or something like that.

Any plans or ideas on this? I'd be interested in helping if I could get an angle on how this might work.

Tinche commented 7 months ago

Howdy,

sure, let's brainstorm! Can you describe a scenario to try to cover?

One solution that comes to mind would be using multiple incanter objects, one for each dependency scope. But maybe it can be simpler.

jriddy commented 7 months ago

Well for now, there is no notion of caching the results of factory functions in Incant at all. So there's no notion of scope anad no obvious place to put it.

An example use case might have something like:


def dsn():
    return DEFAULT_DSN

def db_conn(dsn):
    with dbapi.connect(dsn) as conn: yield conn

def user_types(db_conn):
    return list(map(UserType.from_db_row, db_conn.execute("select * from user_types"))

def print_user_types(user_types):
    for ut in user_types: print(ut)

def main():
    ic = incant.Incanter()
    ic.register_by_name(dsn)
    ic.register_by_name(db_conn)
    ic.register_by_name(user_types)

    # At this point I want to call funtions with db_conn cached between those calls
    composed = ic.compose(print_user_types)

    # Should connect to db and run query
    composed()
    # Should just used cached values
    composed()

    # Should invalidate cache and recalculate all
    composed.reinject(dsn='someother:dsnvalue')

I took a deeper look at the source code that does the generation. Since you just generate calls to functions here, it might be easiest to add some wrapper that can cache, but that wrapper needs to be able to invalidate, based on inputs to the dependency graph, so it's a big thing to put in.

It also seems like the API is geared to treat compose as the heavy-lifting part, but if the actual dependency function invocations are themselves resource intensive, it's like we're missing a bit that lets you pre-calculate them and use their cached values.

jriddy commented 7 months ago

I'm sorry that I'm not defining my use cases, well. I guess I'm more just thinking through these problems as I type them. I think doing this kinda work may be well outside the scope of what you were trying to achieve with Incant.

Tinche commented 7 months ago

I was just typing up a reply when I found a bug in incant running your example ;)

I'll get back to you in a day or two with my thoughts.