Dependency Injection: Python
Dependency injection is just one of those things you need to know when you're a programmer. Read on to learn about DI and/or brush up on your skills.
Join the DZone community and get the full member experience.
Join For Free
Overview
Dependency Injection (DI) is a software engineering technique for defining the dependencies among objects. Basically, the process of supplying a resource that a given piece of code requires. The required resource is called a dependency.
There are various classes and objects defined when writing code. Most of the time, these classes depend on other classes in order to fulfill their intended purpose. These classes, or a better word might be Components, know the resources they need and how to get them. DI handles defining these dependent resources and provides ways to instantiate or create them externally. Dependency Containers are used to implement this behavior and holds the map of dependencies for the components.
If object A depends on object B, object A must not create or import object B directly. Instead, object A must provide a way for injecting object B. The responsibilities of object creation and dependency injection are delegated to external code.
Why Use Dependency Injection in Your Code?
- The flexibility of configurable components: As the components are externally configured, there can be various definitions for a component (Control on application structure).
- Testing Made Easy: Instantiating mock objects and integrating with class definitions is easier.
- High cohesion: Code with reduced module complexity, increased module reusability.
- Minimalistic dependencies: As the dependencies are clearly defined, easier to eliminate/reduce unnecessary dependencies.
Implementing DI in Python
Dependency Injection in Python is little different from general static language DI. Python has a microframework library for DI, called dependency_injector. This package has two main entities: containers and providers.
Providers describe how objects are accessed. Containers are simply a collection of providers. Most commonly used provider types are: Singleton, Configuration, and Factory.
Example
The following example demonstrates the usage and implementation of DI in Python. Create a file named email_client.py containing the EmailClient
class which depends on the object.
class EmailClient(object):
def __init__(self, config):
self._config = config
self.connect(self._config)
def connect(self, config):
# Implement function here
pass
Create a new file named e mail_reader.py which contains the EmailReader
class and depends on the EmailClient
object.
class EmailReader(object):
def __init__(self, client):
try:
self._client = client
except Exception as e:
raise e
def read(self):
# Implement function here
pass
Now to define these above dependencies externally, create a new containers.py file. Import the dependency_injector package and the classes to be used in DI.
from dependency_injector import providers, containers
from email_client import EmailClient
from email_reader import EmailReader
Add the class Configs
to the file. This class is a container with a configuration provider which provides all the configuration objects.
class Configs(containers.DeclarativeContainer):
config = providers.Configuration('config')
# other configs
Add another class, Clients
. This class is a container defining all kinds of clients. EmailClient
is created with a singleton provider, asserting single instances of this class, and defining its dependency on an object.
class Clients(containers.DeclarativeContainer):
email_client = providers.Singleton(EmailClient, Configs.config)
# other clients
The third container is the Readers
class, defining the dependency of EmailReader
class on the EmailClient
class.
class Readers(containers.DeclarativeContainer):
email_reader = providers.Factory(EmailReader, client=Clients.email_client)
# other readers
from dependency_injector import providers, containers
from email_client import EmailClient
from email_reader import EmailReader
class Configs(containers.DeclarativeContainer):
config = providers.Configuration('config')
# other configs
class Clients(containers.DeclarativeContainer):
email_client = providers.Singleton(EmailClient, Configs.config)
# other clients
class Readers(containers.DeclarativeContainer):
email_reader = providers.Factory(EmailReader, client=Clients.email_client)
# other readers
To run the example, create the main.py file with following code.
from containers import Readers, Clients, Configs
if __name__ == "__main__":
Configs.config.override({
"domain_name": "imap.gmail.com",
"email_address": "YOUR_EMAIL_ADDRESS",
"password": "YOUR_PASSWORD",
"mailbox": "INBOX"
})
email_reader = Readers.email_reader()
print email_reader.read()
In the main.py file, the object is overridden with a given dictionary object. The EmailReader
class was instantiated without instantiating the EmailClient
class in the main file, removing the overhead of importing or creating it. That part is taken care by containers file.
For a working example, please refer to my GitHub repo here.
Published at DZone with permission of Shivam Aggarwal. See the original article here.
Opinions expressed by DZone contributors are their own.
Trending
-
Understanding Dependencies...Visually!
-
How To Approach Java, Databases, and SQL [Video]
-
Competing Consumers With Spring Boot and Hazelcast
-
Microservices With Apache Camel and Quarkus (Part 2)
Comments