Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to integrate a notion of scopes #14

Open
jriddy opened this issue Apr 6, 2024 · 4 comments
Open

How to integrate a notion of scopes #14

jriddy opened this issue Apr 6, 2024 · 4 comments

Comments

@jriddy
Copy link

jriddy commented Apr 6, 2024

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
Copy link
Owner

Tinche commented Apr 8, 2024

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
Copy link
Author

jriddy commented Apr 8, 2024

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
Copy link
Author

jriddy commented Apr 8, 2024

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
Copy link
Owner

Tinche commented Apr 8, 2024

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants