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

[question] string-based annotations? equivalent to guice Names.named() and @Named #133

Open
benjaminjbachman opened this issue Jan 22, 2020 · 3 comments

Comments

@benjaminjbachman
Copy link

benjaminjbachman commented Jan 22, 2020

I know it's generally better to use static annotations (eg #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.

@jstasiak
Copy link
Collaborator

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.

@jstasiak
Copy link
Collaborator

jstasiak commented Jan 5, 2021

See #174 for a potential (not supported at the moment) way to use Annotated to achieve this.

@strangemonad
Copy link

@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

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

3 participants