I am learning about Dependency Injection and I have been recently implementing the following classes for an app that executes commands over ssh using Python. I am confused about whether I am using it in the wrong way.
I am using a ConnectionFactory
class to create an ssh
object that I call dynamic, because it depends on the user input (username, password).
Below is a working simplified example that uses injector
and paramiko
packages.
My question is whether it's a code smell, the fact that I am instantiating manually RemoteBashCommandRunner
along with its dependencies.
from injector import Injector, inject
from paramiko import SSHClient, AutoAddPolicy
class A: pass
class B: pass
class C: pass
class D: pass
HOST, USERNAME, PASSWORD = "localhost", "username", "password"
class ConnectionFactory:
@inject
def __init__(self, a: A, b: B):
self.a, self.b = a, b
def get_connection(self, host):
# in a real implementation, this method calls the dependencies set in constructor
ssh = SSHClient()
ssh.set_missing_host_key_policy(AutoAddPolicy())
ssh.connect(host, 22, USERNAME, PASSWORD)
return ssh
class RemoteBashCommandRunner:
def __init__(self, ssh: SSHClient, c: C, d: D) -> None:
self.ssh = ssh
self.c, self.d = c, d
def run(self, command: str):
_, stdout, _ = self.ssh.exec_command(command)
print(stdout.read())
def main():
container = Injector()
connection_factory = container.get(ConnectionFactory)
ssh = connection_factory.get_connection(HOST)
runner = RemoteBashCommandRunner(ssh, C(), D())
runner.run("echo hello")
main()
I have been also thinking about binding the ssh
object in the container and getting an instance of RemoteBashCommandRunner
by using the container
.
...
class RemoteBashCommandRunner:
@inject
def __init__(self, ssh: SSHClient, c: C, d: D) -> None:
...
container.binder.bind(SSHClient, to=lambda: ssh)
container.get(RemoteBashCommandRunner).run("echo hello")
Or maybe just remove the ssh
object from the constructor, and use it as an argument in the method.
class RemoteBashCommandRunner:
@inject
def __init__(self, c: C, d: D) -> None:
def run(self, ssh, command: str):
# no more manual binding
container.get(RemoteBashCommandRunner).run(ssh, "echo hello")
Which one of the 3 approaches is better?