Dependency injection framework for Python
$ pip install deppy
from deppy import container, injectable
@injectable()
class Greeter:
def greet(self) -> str:
return "Hello"
g = container.get(Greeter)
g.greet() # Hello
import abc
from deppy import container, injectable
# Should raise NotImplementedError instead of abc.abstractmethod decorator for abstract method due to following mypy issue:
# https://github.com/python/mypy/issues/5374#issuecomment-582093112
class Greeter(abc.ABC):
def greeter(self) -> str:
raise NotImplementedError()
@injectable(Greeter)
class GreeterImpl(Greeter):
def greeter(self) -> str:
return "Hello"
g = container.get(Greeter)
g.greet() # Hello
# If class is on behalf of abstract class, the class cannot get by itself from container
# check `Multiple implementations` section for more complex use case.
container.get(GreeterImpl) # KeyError
import abc
from enum import Enum
from deppy from container, injectable
class Greeter(abc.ABC):
def greet(self) -> str:
raise NotImplementedError()
class GreeterType(Enum):
A = "A"
B = "B"
@injectable(Greeter, tag=GreeterType.A)
class GreeterA(Greeter):
def greet(self) -> str:
return "A"
@injectable(Greeter, tag=GreeterType.B)
class GreeterB(Greeter):
def greet(self) -> str:
return "B"
a = container.get(Greeter, tag=GreeterType.A)
a.greet() # A
b = container.get(Greeter, tag=GreeterType.B)
b.greet() # B
from deppy import container, injectable
@injectable()
class Greeter:
def greet(self) -> str:
return "Hello"
@injectable()
class GreeterWorld:
def __init__(self, greeter: Greeter):
self._greeter = greeter
def greet(self) -> str:
return self._greeter.greet() + " World"
gw = container.get(GreeterWorld)
gw.greet() # Hello World
import abc
from enum import Enum
from deppy import container, injectable
class Greeter(abc.ABC):
def greet(self) -> str:
raise NotImplementedError()
class GreeterType(Enum):
A = "A"
B = "B"
@injectable(Greeter, tag=GreeterType.A)
class GreeterA(Greeter):
def greet(self) -> str:
return "A"
@injectable(Greeter, tag=GreeterType.B)
class GreeterB(Greeter):
def greet(self) -> str:
return "B"
@injectable(
params={
"a": GreeterType.A,
"b": GreeterType.B
}
)
class GreeterManager:
def __init__(self, a: Greeter, b: Greeter):
self._a = a
self._b = b
def greet(self) -> str:
return self._a.greet() + self._b.greet()
gm = container.get(GreeterManager)
gm.greet() # AB
In order to avoid circular import, it is common to separate interfaces and implementations into different packages.
Package comsumers usually depend only on interfaces,
resulting in no one to import implementations and thus injectable
is never called.
For me, I usually put implemetations under impls
directory besides interfaces. Implemetations can be registered into container by an independent composition root, and just import it and initialize it on application startup:
# composition_root.py
import glob
import importlib
def init():
impls = glob.glob("**/impls", recursive=True)
for impl in impls:
importlib.import_module(impl.replace("/", ".").replace("\\", "."))
© Cyan Ho (pilagod), 2021-NOW
Released under the MIT License