Keys and collections

Several implementations of one type need a qualifier. Several implementations as a set or list use a multibinder.

Qualified keys

Key(Type, Named("...")) is the Guice-style named binding:

from pyiv import Config, get_injector
from pyiv.key import Key, Named

class Database:
    def __init__(self, name: str):
        self.name = name

class PostgreSQL(Database):
    def __init__(self):
        super().__init__("postgresql")

class MySQL(Database):
    def __init__(self):
        super().__init__("mysql")

class MyConfig(Config):
    def configure(self):
        self.register_key(Key(Database, Named("primary")), PostgreSQL)
        self.register_key(Key(Database, Named("replica")), MySQL)

injector = get_injector(MyConfig)
injector.inject(Key(Database, Named("primary")))  # PostgreSQL
injector.inject(Key(Database, Named("replica")))  # MySQL

Binder equivalent: binder.bind_key(Key(Database, Named("primary"))).to(...).

Multibinder

Register many implementations and inject Set[T] or List[T]. Inject the collection through a host class constructor, not injector.inject(Set[T]):

from typing import Set

from pyiv import Config, get_injector

class EventHandler:
    pass

class EmailHandler(EventHandler):
    pass

class SMSHandler(EventHandler):
    pass

class HandlerHost:
    def __init__(self, handlers: Set[EventHandler]):
        self.handlers = handlers

class MyConfig(Config):
    def configure(self):
        mb = self.multibinder(EventHandler, as_set=True)
        mb.add(EmailHandler)
        mb.add(SMSHandler)

host = get_injector(MyConfig).inject(HandlerHost)
# host.handlers is EmailHandler and SMSHandler

as_set=False binds List[T] and preserves add order.

Map multibinder

Keyed implementations inject as Dict[K, V] through a host constructor:

from typing import Dict

from pyiv import Config, get_injector

class Encoder:
    pass

class JsonEncoder(Encoder):
    pass

class XmlEncoder(Encoder):
    pass

class EncoderHost:
    def __init__(self, encoders: Dict[str, Encoder]):
        self.encoders = encoders

class MyConfig(Config):
    def configure(self):
        mb = self.map_multibinder(Encoder)
        mb.add("json", JsonEncoder)
        mb.add("xml", XmlEncoder)

host = get_injector(MyConfig).inject(EncoderHost)
# host.encoders["json"] is JsonEncoder

Optional dependencies

Optional[T] injects the binding or None. Use an ABC (or another type the injector cannot construct) for T. A concrete class with no binding is still built:

from abc import ABC
from typing import Optional

from pyiv import Config, get_injector

class Cache(ABC):
    pass

class Service:
    def __init__(self, cache: Optional[Cache] = None):
        self.cache = cache

class NoCacheConfig(Config):
    def configure(self):
        pass  # Cache not registered

get_injector(NoCacheConfig).inject(Service).cache is None  # True

See also pyiv.key, pyiv.multibinder, and pyiv.optional.