python-injector / injector

Python dependency injection framework, inspired by Guice
BSD 3-Clause "New" or "Revised" License
1.31k stars 81 forks source link

Default provider #96

Open comtihon opened 6 years ago

comtihon commented 6 years ago

Case:

class TokenService(metaclass=ABCMeta):
    @abstractmethod
    def authorize_token(self, auth_header: str) :
        pass

@singleton
class JWTTokenService(TokenService):
...

JWTTokenService is the default implementation for TokenService, even if it has multiple implementations. For now I have to do:

def set_up_injector():
    def token_provider(binder):
        binder.bind(TokenService, JWTTokenService)

    return Injector([token_provider])

It would be nice to have something like @ImplementedBy(JWTTokenService) instead.

Regards, Val

jstasiak commented 6 years ago

Can you provide an example of how would you like your code to look?

comtihon commented 6 years ago

Sure. Java-like parent annotation, but with string:

@implemented_by('JWTTokenService')
class TokenService(metaclass=ABCMeta):
    @abstractmethod
    def authorize_token(self, auth_header: str) :
        pass

@singleton
class JWTTokenService(TokenService):
...

or Child annotation:

class TokenService(metaclass=ABCMeta):
    @abstractmethod
    def authorize_token(self, auth_header: str) :
        pass

@singleton
@default
class JWTTokenService(TokenService):
...
jstasiak commented 6 years ago

I believe the second form is preferable - better editor support (no need to put class name in string form) and easier to provide errors.

I'd change the @default annotation to @default_for(TokenService) - without the explicit interface figuring out what the binding should be can be tricky (in case of multiple inheritance or deep inheritance). @alecthomas do you have opinion on this? I'm still not entirely sure if it's worth it.

The patch introducing this would need to:

alecthomas commented 6 years ago

I like the convenience, it would probably eradicate the need for modules at all in many cases.

I think it would be better as an annotation on the interface, as it makes grokking the bindings much simpler. Without it, tracking down which class is bound to the interface could be quite involved, or even magic depending on imports.

eirikur-grid commented 5 years ago

While having the interface reference the implementation via an annotation is simpler to do, it does result in a circular dependency between the abstraction and the concretion (hence the need for using a string to defer the resolution of the implementing type). I would personally never do something like that. An abstraction should know nothing about its concretions.