injector
injector copied to clipboard
Default provider
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
Can you provide an example of how would you like your code to look?
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):
...
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:
- raise errors when two classes try to register themselves as default implementations for a single interface
- provide extensive test coverage, handle edge cases and various Injector configurations that could affect the dependency injection correctness
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.
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.