injector
injector copied to clipboard
[question] string-based annotations? equivalent to guice Names.named() and @Named
I know it's generally better to use static annotations (eg https://github.com/alecthomas/injector/issues/69), but I found @Named annotations very useful in guice for configuration injection. I'm able to reproduce this feature using type but did I miss an existing implementation in the package? didn't want to reinvent the wheel, even if its only two lines of code.
from injector import Module, Injector, Binder, inject
from dataclasses import dataclass
from collections import defaultdict
def named(name: str, *, seen=defaultdict(lambda: type("", (), {}))):
return seen[name]
@inject
@dataclass
class MyClass:
myval: named("mine")
myotherval: named("somethingelse")
def conf(binder: Binder):
binder.bind(named("mine"), to="test1")
binder.bind(named("somethingelse"), to="test2")
myclass: MyClass = Injector([conf]).get(MyClass)
print(myclass.myval)
print(myclass.myotherval)
From here I'd write a module that parses a config file and does binder.bind(named(key),to=value) to make it easy for classes to get values from the config to an injected class.
No, you're right, something like this is not supported at the moment.
The closest you can get right now is with type aliases I think:
Mine = NewType('Mine', str)
SomethingElse = NewType('SomethingElse', str)
@inject
@dataclass
class MyClass:
myval: Mine
myotherval: SomethingElse
def conf(binder: Binder):
binder.bind(Mine, to="test1")
binder.bind(SomethingElse, to="test2")
# ...
Granted, the types/aliases/names still need to be declared statically up front. If you want to be completely dynamic in config reading I think your other best choice is to have provider methods that receive configuration and construct your classes:
class MyModule(Module):
@provider
def provide_myclass(self, config: Config) -> MyClass:
return MyClass(config['mine'], config['something_else'])
In both of those cases you don't lose type safety if you use any linters that test it, so there's that. :)
Injector has initial support for PEP 593 -- Flexible function and variable annotations so something like Guice's @Named could be implemented now, just no one did it yet.
See https://github.com/alecthomas/injector/issues/174 for a potential (not supported at the moment) way to use Annotated to achieve this.
@jstasiak, just a nit-pick. technically NewType isn't a type alias, it creates a new, distinct type https://docs.python.org/3/library/typing.html#newtype. It's what meta-classes sometimes use under the hood and it's roughly equivalent to what defining a class does under the hood
The following are roughly equivalent (including how it sets the parent class pointer)
class Mine(str): ...
Mine = NewType("Mine", str)
A type alias would be defined as Mine = str https://docs.python.org/3/library/typing.html#type-aliases