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

String provider resolution #150

Merged
merged 7 commits into from
Feb 5, 2025

Conversation

alexanderlazarev0
Copy link
Collaborator

@alexanderlazarev0 alexanderlazarev0 commented Feb 1, 2025

Proposed feature:

Allow dependency injection by specifying the container and provider using a string (does not require import).

class MyContainer(BaseContainer):
    p = providers.Factory(creator)

@inject
def injected(value = Provide["MyContainer.p"]):
    return value

assert injected() == creator()

Why?

  • Resolving circular imports is annoying and sometimes unfeasible for large code-bases, one use of this can really help with this. This pattern is not only supported by other DI frameworks but also by libraries such as 'sqlalchemy`
  • Technically allows hot swap of container (complete decoupling)

To be clear, I don't want to encourage this pattern as standard practice with that-depends but more an option that can help in certain scenarios such as #40.

@alexanderlazarev0
Copy link
Collaborator Author

alexanderlazarev0 commented Feb 1, 2025

@lesnik512 Please take a look at the general feature and let me know what you think about supporting this, the implementation needs to still be slightly reworked to actually do the provider resolution at runtime and not at import time!

In other words, currently I implemented it such that Provide["Container.provider"] instantly tries to resolve the provider (at what I like to refer to as "import-time").

I will rewrite it such that the @inject wrapper actually resolves the provider right before calling the function.

@lesnik512
Copy link
Member

@alexanderlazarev0 I feel bad about it. Don't like to encourage hacks against circular imports instead of refactoring.
And usually trying to avoid unnecessary complexity.

@vrslev What do you think?

@alexanderlazarev0
Copy link
Collaborator Author

alexanderlazarev0 commented Feb 1, 2025

@alexanderlazarev0 I feel bad about it. Don't like to encourage hacks against circular imports instead of refactoring. And usually trying to avoid unnecessary complexity.

@vrslev What do you think?

Yes I agree it's a bit hacky, it's just on a large code base a refactor might need time, and this serves as an easy temp fix. There has been multiple discussion on refactoring and I have experienced this myself with that-depends on many occasions. Also, that-depends currently forces the following coding style:

  • If a module defines a creator function for a provider (which itself is normally inside a container), then dependency injection in that module is not possible (instant circular import).

This is not necessarily bad, but just FYI.

The other point is decoupling, we currently have strong coupling between container and dependencies, and for testing the only feasible approach is to override provider's resolve methods, it is not possible to override a provider itself (for example it is not possible to replace a Singleton provider with a resource provider). Also, override behavior is currently not very representative of the provider since you do not override the creator but instead just the return value (but that can be addressed in a different issue).

@vrslev
Copy link
Collaborator

vrslev commented Feb 1, 2025

I didn’t dive too dip into code, but I suspect this brings some kind of global state to keep track of aliases. That’s the main thing I dislike

@alexanderlazarev0
Copy link
Collaborator Author

alexanderlazarev0 commented Feb 1, 2025

I didn’t dive too dip into code, but I suspect this brings some kind of global state to keep track of aliases. That’s the main thing I dislike

No, I just use the already existing global state, the same way I resolve which resources are in APP scope for example.

Talking about global state there are currently 4 global states:

  1. Global context (separated from the CONTAINER_CONTEXT that was originally used for ContextResources), basically the contextvar that stores container_context(global={})
  2. All Containers are BaseContainerMeta, which stores a list of all containers (also separated from the CONTAINER_CONTEXT), this is the the functionality that supporst container_context(reset_all_containers=True) # notice how you have not specified the containers here
  3. Named scope (just stores the current scope as string)
  4. All the container classes themselves (since the framework relies on the class and not an instance of the class for dependency resolution)

Difficulty to remove (of course the corresponding feature will also no longer be supported):

  1. Global context: easy
  2. List of Containers in BaseContainerMeta: easy to remove, but now you will always have to explicitly list all resources you want to re-initialize context for.
  3. Named scope: will have to be removed with the point above, since it makes no sense otherwise.
  4. Requires a full re-write and removes injection anywhere...

Observations

We can skip the whole discussion of "globals are bad practice" since they are a key feature that enables ease of use and the only way to enable dependency injection anywhere. Now, I try to keep the global's as clean as possible in my implementations, since obviously they can be a potential cause of issues.

So to me, if I am going to use global's at all I am gonna at least abuse them to their full potential (while minimizing the downsides). But let me know your opinion @vrslev and @lesnik512 since I will adjust my work in accordance.

@alexanderlazarev0 alexanderlazarev0 marked this pull request as ready for review February 3, 2025 00:04
@alexanderlazarev0 alexanderlazarev0 added the enhancement New feature or request label Feb 3, 2025
@lesnik512 lesnik512 merged commit b429870 into modern-python:main Feb 5, 2025
5 checks passed
@alexanderlazarev0 alexanderlazarev0 deleted the string_providers branch February 5, 2025 20:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants