I ran into this problem too and after much search I found the answer.
You can inject the callable by using the provider attribute:
import passlib.hash
from dependency_injector import containers, providers
from dependency_injector.wiring import Provide, inject
class Container(containers.DeclarativeContainer):
# Use the 'provider' attribute on the provider in the container
password_verifier = providers.Callable(passlib.hash.sha256_crypt.verify).provider
@inject
def bar(password_verifier=Provide[Container.password_verifier]):
password_verifier(...)
if __name__ == "__main__":
container = Container()
container.wire(modules=[__name__])
bar()
Answer from Aesonus on Stack OverflowI ran into this problem too and after much search I found the answer.
You can inject the callable by using the provider attribute:
import passlib.hash
from dependency_injector import containers, providers
from dependency_injector.wiring import Provide, inject
class Container(containers.DeclarativeContainer):
# Use the 'provider' attribute on the provider in the container
password_verifier = providers.Callable(passlib.hash.sha256_crypt.verify).provider
@inject
def bar(password_verifier=Provide[Container.password_verifier]):
password_verifier(...)
if __name__ == "__main__":
container = Container()
container.wire(modules=[__name__])
bar()
The method passlib.hash.sha256_crypt.verify requires two positional arguments, secret and hash as shown here: https://passlib.readthedocs.io/en/stable/lib/passlib.hash.sha256_crypt.html
Because you're injecting an attribute of the Container, the DI framework must create an instance of this to inject it into the bar() method. The DI framework is unable to instantiate this object however since the required positional arguments are missing.
If you wanted to use the method in bar(), you'd have to do so without provoking the DI framework to try to create an instance of the object at initialisation time. You could do this with the following:
import passlib.hash
from dependency_injector import containers, providers
from dependency_injector.wiring import Provide, inject
class Container(containers.DeclarativeContainer):
password_hasher = providers.Callable(
passlib.hash.sha256_crypt.hash,
salt_size=16,
rounds=10000,
)
password_verifier = providers.Callable(passlib.hash.sha256_crypt.verify)
@inject
def bar(container=Provide[Container]):
hashed_password = container.password_hasher("123")
assert container.password_verifier("123", hashed_password)
if __name__ == "__main__":
container = Container()
container.wire(modules=[__name__])
bar()
» pip install dependency-injector
I've noticed that managing dependencies feels like a boring chore that I want to minimize.
First of all, you shouldn't assume that dependency injection is a means to minimize the chore of dependency management. It doesn't go away, it is just deferred to another place and time and possibly delegated to someone else.
That said, if what you are building is going to be used by others it would thus be wise to include some form of version checking into your 'injectables'so that your users will have an easy way to check if their version matches the one that is expected.
are there more expressive design patterns in existence?
Your method as I understand it is essentially a Strategy-Pattern, that is the job's code (callable) relies on calling methods on one of several concrete objects. The way you do it is perfectly reasonable - it works and is efficient.
You may want to formalize it a bit more to make it easier to read and maintain, e.g.
from collections import namedtuple
Job = namedtuple('Job', ['callable', 'args', 'strategies'])
def run_job(job, using=None):
strategies = { k: using[k] for k in job.strategies] }
return job.callable(*args, **strategies)
jobs_to_run = [
Job(callable=some_func, args=(1,2), strategies=('A', 'B')),
Job(callable=other_func, ...),
]
strategies = {"A": injected_strategy, ...}
for job in jobs_to_run:
run_job(job, using=strategies)
# actual job
def some_func(arg1, arg2, A=None, B=None):
...
As you can see the code still does the same thing, but it is instantly more readable, and it concentrates knowledge about the structure of the Job() objects in run_job. Also the call to a job function like some_func will fail if the wrong number of arguments are passed, and the job functions are easier to code and debug due to their explicitely listed and named arguments.
About the strings you could just make'em constants in a dependencies.py file an use these constants.
An more robust option with still little overhead would to be to use a dependency injection framework such as Injectable:
@autowired
def job42(some_instance: Autowired("SomeInstance", lazy=true)):
...
# some_instance is autowired to job42 calls and
# it will be automatically injected for you
job42()
Disclosure: I am the project maintainer.